Merge "Merge 24Q3 to AOSP main" into main
diff --git a/Android.bp b/Android.bp
index 8bd1bf5..aeaeb8b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -17,10 +17,12 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-min_launcher3_sdk_version = "26"
+min_launcher3_sdk_version = "30"
// Common source files used to build launcher (java and kotlin)
// All sources are split so they can be reused in many other libraries/apps in other folders
+
+// Main Launcher source, excluding the build config
filegroup {
name: "launcher-src",
srcs: [
@@ -29,6 +31,7 @@
],
}
+// Source code for quickstep build, on top of launcher-src
filegroup {
name: "launcher-quickstep-src",
srcs: [
@@ -37,51 +40,20 @@
],
}
+// Alternate source when quickstep is not included
filegroup {
- name: "launcher-go-src",
+ name: "launcher-src_no_quickstep",
srcs: [
- "go/src/**/*.java",
- "go/src/**/*.kt",
+ "src_no_quickstep/**/*.java",
+ "src_no_quickstep/**/*.kt",
],
}
+// Default build config for Launcher3
filegroup {
- name: "launcher-go-quickstep-src",
+ name: "launcher-build-config",
srcs: [
- "go/quickstep/src/**/*.java",
- "go/quickstep/src/**/*.kt",
- ],
-}
-
-filegroup {
- name: "launcher-src_shortcuts_overrides",
- srcs: [
- "src_shortcuts_overrides/**/*.java",
- "src_shortcuts_overrides/**/*.kt",
- ],
-}
-
-filegroup {
- name: "launcher-src_ui_overrides",
- srcs: [
- "src_ui_overrides/**/*.java",
- "src_ui_overrides/**/*.kt",
- ],
-}
-
-filegroup {
- name: "launcher-ext_tests",
- srcs: [
- "ext_tests/**/*.java",
- "ext_tests/**/*.kt",
- ],
-}
-
-filegroup {
- name: "launcher-quickstep-ext_tests",
- srcs: [
- "quickstep/ext_tests/**/*.java",
- "quickstep/ext_tests/**/*.kt",
+ "src_build_config/**/*.java",
],
}
@@ -103,7 +75,7 @@
"androidx.test.uiautomator_uiautomator",
"androidx.preference_preference",
"SystemUISharedLib",
- "animationlib",
+ "//frameworks/libs/systemui:animationlib",
"launcher-testing-shared",
],
srcs: [
@@ -169,6 +141,7 @@
static_libs: [
"LauncherPluginLib",
"launcher_quickstep_log_protos_lite",
+ "android.os.flags-aconfig-java",
"androidx-constraintlayout_constraintlayout",
"androidx.recyclerview_recyclerview",
"androidx.dynamicanimation_dynamicanimation",
@@ -178,39 +151,26 @@
"androidx.cardview_cardview",
"androidx.window_window",
"com.google.android.material_material",
- "iconloader_base",
- "view_capture",
- "animationlib",
- ],
- manifest: "AndroidManifest-common.xml",
- sdk_version: "current",
- min_sdk_version: min_launcher3_sdk_version,
- lint: {
- baseline_filename: "lint-baseline2.xml",
- },
-}
-
-//
-// Build rule for Launcher3 dependencies lib.
-//
-android_library {
- name: "Launcher3CommonDepsLib",
- srcs: ["src_build_config/**/*.java"],
- static_libs: [
+ "//frameworks/libs/systemui:iconloader_base",
+ "//frameworks/libs/systemui:view_capture",
+ "//frameworks/libs/systemui:animationlib",
"SystemUI-statsd",
- "Launcher3ResLib",
"launcher-testing-shared",
- "animationlib",
+ "androidx.lifecycle_lifecycle-common-java8",
+ "androidx.lifecycle_lifecycle-extensions",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "kotlinx_coroutines_android",
+ "kotlinx_coroutines",
"com_android_launcher3_flags_lib",
"com_android_wm_shell_flags_lib",
"android.appwidget.flags-aconfig-java",
"com.android.window.flags.window-aconfig-java",
],
+ manifest: "AndroidManifest-common.xml",
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
- manifest: "AndroidManifest-common.xml",
lint: {
- baseline_filename: "lint-baseline2.xml",
+ baseline_filename: "lint-baseline.xml",
},
}
@@ -221,17 +181,14 @@
name: "Launcher3",
static_libs: [
- "Launcher3CommonDepsLib",
+ "Launcher3ResLib",
],
srcs: [
":launcher-src",
- ":launcher-src_shortcuts_overrides",
- ":launcher-src_ui_overrides",
- ":launcher-ext_tests",
+ ":launcher-src_no_quickstep",
+ ":launcher-build-config",
],
- resource_dirs: [
- "ext_tests/res",
- ],
+
optimize: {
proguard_flags_files: ["proguard.flags"],
// Proguard is disable for testing. Derivarive prjects to keep proguard enabled
@@ -276,24 +233,21 @@
"lottie",
"SystemUISharedLib",
"SettingsLibSettingsTheme",
- "SystemUI-statsd",
- "animationlib",
],
manifest: "quickstep/AndroidManifest.xml",
min_sdk_version: "current",
}
-// Library with all the dependencies for building Launcher Go
+// Library with all the source code and dependencies for building Launcher Go
android_library {
- name: "LauncherGoResLib",
+ name: "Launcher3GoLib",
srcs: [
":launcher-src",
":launcher-quickstep-src",
- ":launcher-go-src",
- ":launcher-go-quickstep-src",
+ "go/quickstep/src/**/*.java",
+ "go/quickstep/src/**/*.kt",
],
resource_dirs: [
- "go/res",
"go/quickstep/res",
],
// Note the ordering here is important when it comes to resource
@@ -301,7 +255,6 @@
// in QuickstepResLib to take precendece, so it should be the final
// dependency. See b/205278434 for how this can go wrong.
static_libs: [
- "Launcher3CommonDepsLib",
"QuickstepResLib",
"androidx.room_room-runtime",
],
@@ -316,13 +269,13 @@
use_resource_processor: false,
}
-// Build rule for Quickstep library
+// Library with all the source code and dependencies for building Quickstep
android_library {
name: "Launcher3QuickStepLib",
srcs: [
":launcher-src",
":launcher-quickstep-src",
- ":launcher-src_shortcuts_overrides",
+ ":launcher-build-config",
],
resource_dirs: [],
libs: [
@@ -334,10 +287,7 @@
// dependency. See b/208647810 for how this can go wrong.
static_libs: [
"SystemUI-statsd",
- "SystemUISharedLib",
- "Launcher3CommonDepsLib",
"QuickstepResLib",
- "animationlib",
],
manifest: "quickstep/AndroidManifest.xml",
platform_apis: true,
@@ -346,49 +296,6 @@
use_resource_processor: false,
}
-// Build rule for Launcher3 Go app for Android Go devices.
-android_app {
- name: "Launcher3Go",
-
- static_libs: ["Launcher3CommonDepsLib"],
-
- srcs: [
- ":launcher-src",
- ":launcher-go-src",
- ":launcher-src_ui_overrides",
- ],
-
- resource_dirs: ["go/res"],
-
- optimize: {
- proguard_flags_files: ["proguard.flags"],
- },
-
- sdk_version: "current",
- min_sdk_version: "current",
- target_sdk_version: "current",
- privileged: true,
- system_ext_specific: true,
- overrides: [
- "Home",
- "Launcher2",
- "Launcher3",
- "Launcher3QuickStep",
- ],
- required: ["privapp_whitelist_com.android.launcher3"],
-
- additional_manifests: [
- "AndroidManifest.xml",
- "AndroidManifest-common.xml",
- ],
-
- manifest: "go/AndroidManifest.xml",
- jacoco: {
- include_filter: ["com.android.launcher3.*"],
- },
-
-}
-
// Build rule for Quickstep app.
android_app {
name: "Launcher3QuickStep",
@@ -425,28 +332,20 @@
}
-// Build rule for Launcher3 Go app with quickstep for Android Go devices.
-android_app {
- name: "Launcher3QuickStepGo",
- static_libs: [
- "SystemUI-statsd",
- "SystemUISharedLib",
- "LauncherGoResLib",
- ],
+// Build rule for Launcher3 Go app with quickstep for Android Go devices.
+// Note that the following two rules are exactly same, and should
+// eventually be merged into a single target
+android_app {
+ name: "Launcher3Go",
+
+ static_libs: ["Launcher3GoLib"],
+ resource_dirs: [],
platform_apis: true,
min_sdk_version: "current",
target_sdk_version: "current",
- srcs: [],
-
- resource_dirs: [
- "go/quickstep/res",
- "go/res",
- "quickstep/res",
- ],
-
optimize: {
proguard_flags_files: ["proguard.flags"],
enabled: true,
@@ -472,5 +371,40 @@
jacoco: {
include_filter: ["com.android.launcher3.*"],
},
+}
+android_app {
+ name: "Launcher3QuickStepGo",
+ static_libs: ["Launcher3GoLib"],
+ resource_dirs: [],
+
+ platform_apis: true,
+ min_sdk_version: "current",
+ target_sdk_version: "current",
+
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ enabled: true,
+ },
+
+ privileged: true,
+ system_ext_specific: true,
+ overrides: [
+ "Home",
+ "Launcher2",
+ "Launcher3",
+ "Launcher3QuickStep",
+ ],
+ required: ["privapp_whitelist_com.android.launcher3"],
+
+ additional_manifests: [
+ "go/AndroidManifest.xml",
+ "go/AndroidManifest-launcher.xml",
+ "AndroidManifest-common.xml",
+ ],
+
+ manifest: "quickstep/AndroidManifest.xml",
+ jacoco: {
+ include_filter: ["com.android.launcher3.*"],
+ },
}
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index a31ee80..80d2eac 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -52,18 +52,18 @@
name in the permissions. eq com.mypackage.permission.READ_SETTINGS
-->
<permission
- android:name="${packageName}.permission.READ_SETTINGS"
+ android:name="${applicationId}.permission.READ_SETTINGS"
android:protectionLevel="signatureOrSystem"
android:label="@string/permlab_read_settings"
android:description="@string/permdesc_read_settings"/>
<permission
- android:name="${packageName}.permission.WRITE_SETTINGS"
+ android:name="${applicationId}.permission.WRITE_SETTINGS"
android:protectionLevel="signatureOrSystem"
android:label="@string/permlab_write_settings"
android:description="@string/permdesc_write_settings"/>
- <uses-permission android:name="${packageName}.permission.READ_SETTINGS" />
- <uses-permission android:name="${packageName}.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="${applicationId}.permission.READ_SETTINGS" />
+ <uses-permission android:name="${applicationId}.permission.WRITE_SETTINGS" />
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
@@ -126,10 +126,10 @@
-->
<provider
android:name="com.android.launcher3.LauncherProvider"
- android:authorities="${packageName}.settings"
+ android:authorities="${applicationId}.settings"
android:exported="true"
- android:writePermission="${packageName}.permission.WRITE_SETTINGS"
- android:readPermission="${packageName}.permission.READ_SETTINGS" />
+ android:writePermission="${applicationId}.permission.WRITE_SETTINGS"
+ android:readPermission="${applicationId}.permission.READ_SETTINGS" />
<!--
The content provider for exposing various launcher grid options.
@@ -137,7 +137,7 @@
-->
<provider
android:name="com.android.launcher3.graphics.GridCustomizationsProvider"
- android:authorities="${packageName}.grid_control"
+ android:authorities="${applicationId}.grid_control"
android:exported="true" />
<!--
@@ -157,7 +157,7 @@
<provider
android:name="com.android.launcher3.testing.TestInformationProvider"
- android:authorities="${packageName}.TestInfo"
+ android:authorities="${applicationId}.TestInfo"
android:readPermission="android.permission.WRITE_SECURE_SETTINGS"
android:writePermission="android.permission.WRITE_SECURE_SETTINGS"
android:exported="true"
@@ -179,7 +179,7 @@
</intent-filter>
</activity>
- <!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
+ <!-- Disable eager initialization of Jetpack libraries. See bug 197780098. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
diff --git a/OWNERS b/OWNERS
index 31e4610..a66bf54 100644
--- a/OWNERS
+++ b/OWNERS
@@ -11,6 +11,7 @@
[email protected]
[email protected]
[email protected]
[email protected]
# Launcher workspace eng team
[email protected]
@@ -27,6 +28,8 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
# Overview eng team
[email protected]
@@ -38,5 +41,15 @@
[email protected]
[email protected]
+# Widget Picker team
[email protected]
[email protected]
+
per-file FeatureFlags.java, globs = set noparent
per-file FeatureFlags.java = [email protected], [email protected], [email protected], [email protected], [email protected]
+
+per-file DeviceConfigWrapper.java, globs = set noparent
+per-file DeviceConfigWrapper.java = [email protected], [email protected], [email protected], [email protected]
+
+# Predictive Back
+per-file LauncherBackAnimationController.java = [email protected], [email protected]
\ No newline at end of file
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 3d15e77..9051ca8 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,13 @@
+[Builtin Hooks]
+ktfmt = true
+
+[Builtin Hooks Options]
+ktfmt = --kotlinlang-style
+
+[Tool Paths]
+ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh
+
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT}
-ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check ${PREUPLOAD_FILES}
-
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 462d947..f1f9966 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -79,6 +79,13 @@
}
flag {
+ name: "enable_taskbar_customization"
+ namespace: "launcher"
+ description: "Enables taskbar customization framework."
+ bug: "347281365"
+}
+
+flag {
name: "enable_unfolded_two_pane_picker"
namespace: "launcher"
description: "Enables two pane widget picker for unfolded foldables"
@@ -171,6 +178,13 @@
}
flag {
+ name: "force_monochrome_app_icons"
+ namespace: "launcher"
+ description: "Enable the ability to generate monochromatic icons, if it is not provided by the app"
+ bug: "270396209"
+}
+
+flag {
name: "enable_add_app_widget_via_config_activity_v2"
namespace: "launcher"
description: "When adding app widget through config activity, directly add it to workspace to reduce flicker"
@@ -188,21 +202,117 @@
}
flag {
- name: "grid_migration_fix"
+ name: "enable_grid_migration_fix"
namespace: "launcher"
description: "Keep items in place when migrating to a bigger grid"
bug: "325286145"
+ is_fixed_read_only: true
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
- name: "narrow_grid_restore"
+ name: "enable_narrow_grid_restore"
namespace: "launcher"
description: "Using only the most recent workspace when restoring to avoid confusion."
+ is_fixed_read_only: true
bug: "325285743"
metadata {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_scaling_reveal_home_animation"
+ namespace: "launcher"
+ description: "Enables the Home gesture animation"
+ bug: "308801666"
+}
+
+flag {
+ name: "enable_widget_tap_to_add"
+ namespace: "launcher"
+ description: "Enables an add button in the widget picker"
+ bug: "323886237"
+}
+
+flag {
+ name: "enable_refactor_task_thumbnail"
+ namespace: "launcher"
+ description: "Enables rewritten version of TaskThumbnailViews in Overview"
+ bug: "331753115"
+}
+
+flag {
+ name: "enable_handle_delayed_gesture_callbacks"
+ namespace: "launcher"
+ description: "Enables additional handling for delayed mid-gesture callbacks"
+ bug: "285636175"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_fallback_overview_in_window"
+ namespace: "launcher"
+ description: "Enables fallback recents opening inside of a window instead of an activity."
+ bug: "292269949"
+}
+
+flag {
+ name: "enable_smartspace_as_a_widget"
+ namespace: "launcher"
+ description: "Enables smartspace as a widget"
+ bug: "300140279"
+}
+
+flag {
+ name: "enable_smartspace_removal_toggle"
+ namespace: "launcher"
+ description: "Enables smartspace removal toggle"
+ bug: "303471576"
+}
+
+flag {
+ name: "enable_additional_home_animations"
+ namespace: "launcher"
+ description: "Enables custom home animations for non-running tasks"
+ bug: "237638627"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enabled_folders_in_all_apps"
+ namespace: "launcher"
+ description: "Enables folders in all apps"
+ bug: "341582436"
+}
+
+flag {
+ name: "enable_recents_in_taskbar"
+ namespace: "launcher"
+ description: "Replace hybrid hotseat app predictions with strictly Recent Apps"
+ bug: "315354060"
+}
+
+flag {
+ name: "enable_first_screen_broadcast_archiving_extras"
+ namespace: "launcher"
+ description: "adds Extras to first screen broadcast for archived apps"
+ bug: "322314760"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "floating_search_bar"
+ namespace: "launcher"
+ description: "Search bar persists at the bottom of the screen across Launcher states"
+ bug: "346408388"
+}
diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig
index 31d8d34..b98eee6 100644
--- a/aconfig/launcher_search.aconfig
+++ b/aconfig/launcher_search.aconfig
@@ -42,3 +42,14 @@
description: "This flag disables drag and drop for Private Space Items."
bug: "289223923"
}
+
+
+flag {
+ name: "private_space_add_floating_mask_view"
+ namespace: "launcher_search"
+ description: "This flag enables the floating mask view as part of the Private Space animation. "
+ bug: "339850589"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/buglist.txt b/buglist.txt
deleted file mode 100644
index 53dcc35..0000000
--- a/buglist.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-171450807
-170675311
-170338029
-170338170
-160544577
-171171594
-170488559
-171131394
-171131394
-171026321
-170648272
-170752716
-170611866
-170702596
-170487752
-170665892
-168608912
-170636685
-169771796
-141126144
-166614700
-168805872
-170263425
-169221288
-143965596
-169221287
-167259591
-156044202
-169438169
-164926736
-168653219
-169963211
-170121063
-169988381
-169980192
-169221288
-169385783
-168167693
-169796517
-169330678
-168818961
-168608912
diff --git a/buglist_unique.txt b/buglist_unique.txt
deleted file mode 100644
index 93dbefb..0000000
--- a/buglist_unique.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-141126144
-143965596
-156044202
-160544577
-164926736
-166614700
-167259591
-168167693
-168608912
-168653219
-168805872
-168818961
-169221287
-169221288
-169330678
-169385783
-169438169
-169771796
-169796517
-169963211
-169980192
-169988381
-170121063
-170263425
-170338029
-170338170
-170487752
-170488559
-170611866
-170636685
-170648272
-170665892
-170675311
-170702596
-170752716
-171026321
-171131394
-171171594
-171450807
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index a453bfd..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,188 +0,0 @@
-buildscript {
- repositories {
- mavenCentral()
- google()
- }
- dependencies {
- classpath GRADLE_CLASS_PATH
- classpath PROTOBUF_CLASS_PATH
- }
-}
-
-final String ANDROID_TOP = "${rootDir}/../../.."
-final String FRAMEWORK_PREBUILTS_DIR = "${ANDROID_TOP}/prebuilts/framework_intermediates/"
-
-apply plugin: 'com.android.application'
-apply plugin: 'com.google.protobuf'
-
-android {
- compileSdkVersion COMPILE_SDK
- buildToolsVersion BUILD_TOOLS_VERSION
-
- defaultConfig {
- minSdkVersion 30
- targetSdkVersion 33
- versionCode 1
- versionName "1.0"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- vectorDrawables.useSupportLibrary = true
- }
- buildTypes {
- debug {
- minifyEnabled false
- }
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-
- // The flavor dimensions for build variants (e.g. aospWithQuickstep, aospWithoutQuickstep)
- // See: https://developer.android.com/studio/build/build-variants#flavor-dimensions
- flavorDimensions "app", "recents"
-
- productFlavors {
- aosp {
- dimension "app"
- applicationId 'com.android.launcher3'
- testApplicationId 'com.android.launcher3.tests'
- }
-
- l3go {
- dimension "app"
- applicationId 'com.android.launcher3'
- testApplicationId 'com.android.launcher3.tests'
- }
-
- withQuickstep {
- dimension "recents"
-
- minSdkVersion 28
- }
-
- withoutQuickstep {
- dimension "recents"
- }
- }
-
- // Disable release builds for now
- android.variantFilter { variant ->
- if (variant.buildType.name.endsWith('release')) {
- variant.setIgnore(true)
- }
- }
-
- sourceSets {
- main {
- res.srcDirs = ['res']
- java.srcDirs = ['src', 'src_plugins']
- manifest.srcFile 'AndroidManifest-common.xml'
- proto {
- srcDirs = ['protos/', 'protos_overrides/']
- }
- }
-
- androidTest {
- res.srcDirs = ['tests/res']
- java.srcDirs = ['tests/src', 'tests/tapl']
- manifest.srcFile "tests/AndroidManifest-common.xml"
- }
-
- androidTestDebug {
- manifest.srcFile "tests/AndroidManifest.xml"
- }
-
- aosp {
- java.srcDirs = ['src_flags', 'src_shortcuts_overrides']
- }
-
- aospWithoutQuickstep {
- manifest.srcFile "AndroidManifest.xml"
- }
-
- aospWithQuickstep {
- manifest.srcFile "quickstep/AndroidManifest-launcher.xml"
- }
-
- l3go {
- res.srcDirs = ['go/res']
- java.srcDirs = ['go/src']
- manifest.srcFile "go/AndroidManifest.xml"
- }
-
- l3goWithoutQuickstepDebug {
- manifest.srcFile "AndroidManifest.xml"
- }
-
- l3goWithQuickstepDebug {
- manifest.srcFile "quickstep/AndroidManifest-launcher.xml"
- }
-
- withoutQuickstep {
- java.srcDirs = ['src_ui_overrides']
- }
-
- withQuickstep {
- res.srcDirs = ['quickstep/res', 'quickstep/recents_ui_overrides/res']
- java.srcDirs = ['quickstep/src', 'quickstep/recents_ui_overrides/src']
- manifest.srcFile "quickstep/AndroidManifest.xml"
- }
- }
-}
-
-allprojects {
- repositories {
- maven { url "../../../prebuilts/sdk/current/androidx/m2repository" }
- maven { url "../../../prebuilts/fullsdk-darwin/extras/android/m2repository" }
- maven { url "../../../prebuilts/fullsdk-linux/extras/android/m2repository" }
- mavenCentral()
- google()
- }
-}
-
-dependencies {
- implementation "androidx.dynamicanimation:dynamicanimation:${ANDROID_X_VERSION}"
- implementation "androidx.recyclerview:recyclerview:${ANDROID_X_VERSION}"
- implementation "androidx.preference:preference:${ANDROID_X_VERSION}"
- implementation project(':IconLoader')
- implementation project(':UiTestsLibLauncher')
- withQuickstepImplementation project(':SharedLibWrapper')
-
- // Recents lib dependency
- withQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
-
- // Required for AOSP to compile. This is already included in the sysui_shared.jar
- withoutQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'plugin_core.jar')
-
- testImplementation 'junit:junit:4.12'
- testImplementation libs.mockitoInlineExtended
- androidTestImplementation libs.mockitoInlineExtended
- androidTestImplementation "org.mockito:mockito-core:1.9.5"
- androidTestImplementation 'com.google.dexmaker:dexmaker:1.2'
- androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2'
- androidTestImplementation 'com.android.support.test:runner:1.0.0'
- androidTestImplementation 'com.android.support.test:rules:1.0.0'
- androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
- androidTestImplementation "androidx.annotation:annotation:${ANDROID_X_VERSION}"
-
- api 'com.airbnb.android:lottie:3.3.0'
-}
-
-protobuf {
- // Configure the protoc executable
- protoc {
- artifact = "com.google.protobuf:protoc:${protocVersion}${PROTO_ARCH_SUFFIX}"
- }
- generateProtoTasks {
- all().each { task ->
- task.builtins {
- remove java
- java {
- option "lite"
- }
- }
- }
- }
-}
diff --git a/commitlist.txt b/commitlist.txt
deleted file mode 100644
index 27b8bac..0000000
--- a/commitlist.txt
+++ /dev/null
@@ -1,934 +0,0 @@
-[34mCOMMAND>> git log f99351888c3e5a128559678304fefd647472bc7f..4c3952dc60fc78d3816012a86d7e71747ef34c74[m
-commit 4c3952dc60fc78d3816012a86d7e71747ef34c74
-Merge: cb403d9e5 70e8b1572
-Author: TreeHugger Robot <[email protected]>
-Date: Fri Oct 23 00:27:39 2020 +0000
-
- Merge "Minor stylistic changes in Workspace.java." into ub-launcher3-master
-
-commit cb403d9e5235c7323bc2fdffe6a264d17bb6d0a6
-Author: Pinyao Ting <[email protected]>
-Date: Thu Oct 22 16:07:08 2020 -0700
-
- flip default value of minimal device feature flag
-
- Test: manual
- Change-Id: Iaf46dffb935bdf4b46e7c57d547bdc697250ec56
-
-commit a97557a15eb111616d868120a9f4659f1b451fa2
-Merge: f5ce80b8a 932a327eb
-Author: Tracy Zhou <[email protected]>
-Date: Thu Oct 22 18:22:56 2020 +0000
-
- Merge "Consider overscroll adjustment of RecentsView for live tile" into ub-launcher3-master
-
-commit 932a327ebf0587b8324b9fea7d31328b2f6719a8
-Author: Tracy Zhou <[email protected]>
-Date: Wed Oct 21 23:29:00 2020 -0700
-
- Consider overscroll adjustment of RecentsView for live tile
-
- Fixes: 171450807
- Test: manual
- Change-Id: I83eebf1f6b61c67f289db51aabe5a971815d0df1
-
-commit f5ce80b8a0a1636fc8159475177a07b281492c88
-Author: Hilary Huo <[email protected]>
-Date: Wed Oct 14 16:35:55 2020 -0700
-
- [pixel-search] Latency analysis, add logging statement in launcher
-
- Bug: b/170675311
- Change-Id: I229ace399085bea1c3f9535eb713edd329dff8bd
-
-commit 31b03941ef3aa17edc08c1b509d4fa23766f2d2c
-Merge: e0a50c9e3 0731273d5
-Author: Tracy Zhou <[email protected]>
-Date: Wed Oct 21 20:03:57 2020 +0000
-
- Merge "Track live tile better by considering resistance animation" into ub-launcher3-master
-
-commit 0731273d5409149fca32dfb2ad76eab45f6ea79a
-Author: Tracy Zhou <[email protected]>
-Date: Wed Oct 21 12:03:40 2020 -0700
-
- Track live tile better by considering resistance animation
-
- Fixes: 170338029
- Test: Manual
- Change-Id: I66536bae567aa94385d5e0352cec9d46d512927a
-
-commit e0a50c9e3f1d4b9f113d6afae01ff2c4ed452fba
-Merge: d2c27a595 acfac6187
-Author: Alex Chau <[email protected]>
-Date: Wed Oct 21 17:02:57 2020 +0000
-
- Merge "Use Diplay.getMetrics in DisplayController" into ub-launcher3-master
-
-commit d2c27a595065d43bbea37dd2a512d37080f5233e
-Merge: ff8febabb 8b488ccc2
-Author: Tracy Zhou <[email protected]>
-Date: Wed Oct 21 07:19:48 2020 +0000
-
- Merge "[Live Tile] Support launching running task animation" into ub-launcher3-master
-
-commit 8b488ccc2e433a708c8b06f0b6866f2a305e4b0a
-Author: Tracy Zhou <[email protected]>
-Date: Wed Oct 14 12:13:04 2020 -0700
-
- [Live Tile] Support launching running task animation
-
- Fixes: 170338170
- Test: manual
- Change-Id: I2526b7cfbacaea7899b8e2ed233f913630071d36
-
-commit 70e8b157219e9090ba5e47fdfa51b2b92e98449d
-Author: Andy Wickham <[email protected]>
-Date: Wed Oct 7 23:00:06 2020 -0700
-
- Minor stylistic changes in Workspace.java.
-
- Change-Id: Ib07611f27cbc427d11abccd8b74ea144485752f7
-
-commit acfac6187dd9d13d55b566a77a5da867a1813573
-Author: Alex Chau <[email protected]>
-Date: Mon Oct 19 18:00:39 2020 +0100
-
- Use Diplay.getMetrics in DisplayController
-
- - This is a workaround of b/163815566, where DisplayMetrics is stale
- when onDisplayChanged is called.
- - Instead of relying on stale DisplayConext, get the DisplayMetrics
- from the Display directly.
- - Also optimized how DisplayController.Info is created by passing in
- Display only
- - Use mDisplayContext.getDisplay directly if availalbe
-
- Bug: 163815566, 160544577
- Test: DPI looks correct on device boot
- Change-Id: I2a7454bb8cf2073ce592e8662781b87fc998444f
- (cherry picked from commit 177c38243dc3bf245d1f7db3c265dfb56522f441)
-
-commit ff8febabb039a3c27ee068f85119860a048b917c
-Merge: b03d2b416 102746823
-Author: TreeHugger Robot <[email protected]>
-Date: Tue Oct 20 17:46:09 2020 +0000
-
- Merge "Makes Plugin Settings gear adjust to dark mode." into ub-launcher3-master
-
-commit b03d2b41616d479ba360fa4f97e57722c7f57b8e
-Merge: fb79f5541 caa1e9c39
-Author: Hyunyoung Song <[email protected]>
-Date: Tue Oct 20 15:25:56 2020 +0000
-
- Merge "Search query method should support multiple consumers" into ub-launcher3-master
-
-commit fb79f5541dcbe587002756bb40a3c632d38cc25a
-Merge: f6b05068d cf0b275a4
-Author: Schneider Victor-tulias <[email protected]>
-Date: Tue Oct 20 13:51:06 2020 +0000
-
- Merge "Add the ability to specify a list of tutorial steps in the gesture sandbox tutorial intent." into ub-launcher3-master
-
-commit f6b05068d901d4e989b2e107c06f9c7a6e7b113f
-Author: Hyunyoung Song <[email protected]>
-Date: Tue Oct 20 00:19:29 2020 -0700
-
- Invert the badging
-
- Bug: 171171594
- Change-Id: If84fdc03254105c843e16f39f479505b16e1cd5f
-
-commit caa1e9c39978cb3b467b5ac441eb39b5e883fa2e
-Author: Hyunyoung Song <[email protected]>
-Date: Mon Oct 12 13:56:02 2020 -0700
-
- Search query method should support multiple consumers
-
- Bug: 170488559
- Change-Id: I64bef9523d3c3950c4ca3a4b9ce1d506d1672200
-
-commit 10274682339bb60cb24c50536b4f48f921970f3c
-Author: Andy Wickham <[email protected]>
-Date: Mon Oct 19 19:06:52 2020 -0700
-
- Makes Plugin Settings gear adjust to dark mode.
-
- It wasn't visible in dark mode before because it was
- black on black. This makes it adjust automatically.
-
- Change-Id: I5176cffc01842509ddafc4f30ff5029a0c4b8050
-
-commit 744a0fbeae8efaa942d21c61e25012d86f5ff81e
-Merge: 29c79947e 71f24588c
-Author: TreeHugger Robot <[email protected]>
-Date: Mon Oct 19 22:17:26 2020 +0000
-
- Merge "Call click event on IME quick select for SearchResultIcon" into ub-launcher3-master
-
-commit 29c79947ecf82f662d02004ba9a7289017fc0783
-Merge: 13a2a010d a68ac3e5d
-Author: TreeHugger Robot <[email protected]>
-Date: Mon Oct 19 18:42:05 2020 +0000
-
- Merge "Removing condition for CUJ tracing/metrics" into ub-launcher3-master
-
-commit 71f24588c0a66449a0c68bcb360a8c671914ce75
-Author: Samuel Fufa <[email protected]>
-Date: Mon Oct 19 10:19:31 2020 -0700
-
- Call click event on IME quick select for SearchResultIcon
-
- Bug: 171131394
- Change-Id: I8a703e8d0ca10570e3f774510610d3fb4c0eaab8
-
-commit 13a2a010decd87eeaf8932430c692f587d2de165
-Author: Samuel Fufa <[email protected]>
-Date: Sun Oct 18 21:19:57 2020 -0700
-
- Handle IME event for SearchResultIcon
-
- Bug: 171131394
- Test: Manual
- Change-Id: I2ed1c61053c78aaecc3324418229d69634a72ae4
-
-commit 1f79eeda76246534697e92740defc7f73c3c8d14
-Author: Samuel Fufa <[email protected]>
-Date: Fri Oct 16 02:01:31 2020 -0700
-
- Remove hardcoded itemTypes from SearchTarget
-
- - Introduces componentName and userHandle members to SearchTarget
- - SearchTargetEvent now has searchTarget member
- - Builder pattern for SearchTarget and SearchTargetEvent
- - Search backend should add headers manually instead of launcher inferring sections
-
- Bug: 171026321
- Test: Manual
- Change-Id: I28e0455e82b925277a17703b9aa061c8f9f15262
-
-commit a68ac3e5dd23095cea7c872c0ff1c5042d1695ba
-Author: vadimt <[email protected]>
-Date: Fri Oct 16 10:48:28 2020 -0700
-
- Removing condition for CUJ tracing/metrics
-
- Is doesn't reflect whether jank monitors is collecting metrics,
- which will eventually be always true anyways.
-
- Change-Id: Iaebdc838ed2b2cebd32c8c48d7e45bdd93f76fb4
-
-commit 9228ff53c2fb26850b7bd92d86214a6aaebb11d3
-Author: Sunny Goyal <[email protected]>
-Date: Mon Oct 12 13:43:51 2020 -0700
-
- Trimming activity and task label
-
- Bug: 170648272
- Change-Id: Icd099acee65305e0aa0f98a2a301a0df8a27cf07
-
-commit 7a09177e500a53205f9969bb6cbd4251d54e8fde
-Merge: 37ed5ead3 314761a80
-Author: TreeHugger Robot <[email protected]>
-Date: Thu Oct 15 22:36:14 2020 +0000
-
- Merge "Setup SearchResultIcon for single cell results" into ub-launcher3-master
-
-commit 37ed5ead391df5747003b2d3a345be0347362f19
-Merge: d5bbe6809 702ed2788
-Author: TreeHugger Robot <[email protected]>
-Date: Thu Oct 15 22:06:12 2020 +0000
-
- Merge "Fix the issue where shortcuts are removed in minimal device mode" into ub-launcher3-master
-
-commit 314761a80819a6e64a136161f51eebb0f0528c4d
-Author: Samuel Fufa <[email protected]>
-Date: Wed Oct 14 10:15:07 2020 -0700
-
- Setup SearchResultIcon for single cell results
-
- SearchResultIcon will be able to render apps, shortcuts and remote actions. It can also handle its own focused state drawing.
-
- Screenshot: https://screenshot.googleplex.com/C3KgjJtLQTBPgaf
-
- Bug: 170752716
- Test: Manual
- Change-Id: I460a9c128ea3f5814784e342c5d5fa5b7e310882
-
-commit 702ed2788678ac744c768aad6a6302e7cf91a26b
-Author: Pinyao Ting <[email protected]>
-Date: Wed Oct 14 11:17:04 2020 -0700
-
- Fix the issue where shortcuts are removed in minimal device mode
-
- When loading the workspace, Launcher pins/unpins shortcuts in comply
- with the loaded workspace. Since minimal device mode creates a mostly
- empty workspace, existing shortcuts are getting unpinned as a result.
-
- To mitigate the issue this CL compares the db name and only invoke
- sanitizeData when it matches the one defined in InvariantDeviceProfile.
-
- Bug: 170611866
- Test: manual
- 1. add some deep shortcut in workspace (e.g. long tap on chrome, drag
- "incognito tab" to workspace)
- 2. opt-in to sunshine fishfood (g/sunshine-teamfood)
- 3. enable bedtime mode with minimal device in Settings -> Digital
- Wellbeing -> Show Your Data -> Bedtime mode -> Customize -> minimal
- device
- 4. toggle bedtime mode, wait for apps in minimal device to show, then
- toggle off bedtime mode
- 5. verify the deep shortcut still exist
-
- Change-Id: Ie18216ecb288e7481aa2404c4cb3ea418aee85cb
-
-commit cf0b275a48d3c9f91a346f7fc24b9604f6dde25a
-Author: Schneider Victor-tulias <[email protected]>
-Date: Tue Oct 6 09:33:40 2020 -0400
-
- Add the ability to specify a list of tutorial steps in the gesture sandbox tutorial intent.
-
- Added tutorial_steps string array in the intent to allow specifying an ordered list of tutorial steps.
-
- Change-Id: Ic42a65598a74a64f8441a22f58c6cd988a5762e3
-
-commit d5bbe6809dcc056fbfc307909b171651f0fb3044
-Author: Samuel Fufa <[email protected]>
-Date: Wed Oct 14 15:39:38 2020 -0700
-
- Rename shrotcut container to deep-shrotcuts
-
- Change-Id: If94f0dfa447235f3b1a652f7b6c749695b42d97c
-
-commit 26c1105fa04c2bcc156051e51df90a6a253349bb
-Author: Samuel Fufa <[email protected]>
-Date: Tue Oct 13 01:12:03 2020 -0700
-
- [search api part 1] Setup centralized SearchEventTracker
-
- - Rename AdapterItemWIthPayload to SearchAdapterItem, PayloadResultHandler to SearchTargetHandler
- - Setup SliceViewWrapper for self contained slices
-
- Bug: 170702596
- Change-Id: I0baf984ec8123c95011abcc17372f8d055e98ad7
-
-commit 057f2d0d7df67e3680e479ac76b48b30d8bcf884
-Merge: 4bb65ff51 9a6145efb
-Author: TreeHugger Robot <[email protected]>
-Date: Tue Oct 13 01:57:47 2020 +0000
-
- Merge "Introduce shortcut container for hotseat event reporting" into ub-launcher3-master
-
-commit 4bb65ff516c6d9a429971ab7e04780792d5cb751
-Merge: 69740e62b 2afcab804
-Author: TreeHugger Robot <[email protected]>
-Date: Tue Oct 13 00:07:36 2020 +0000
-
- Merge "Search UI clean up" into ub-launcher3-master
-
-commit 2afcab804b638ff3b9da5bad40c8f70bdcaae78d
-Author: Samuel Fufa <[email protected]>
-Date: Mon Oct 12 15:38:14 2020 -0700
-
- Search UI clean up
-
- - Resolve spacing issue when work profile is installed
- - Cache play icons and use icon shape
- - Only draw focus indicator for the first result
-
- Bug: 170487752
- Bug: 170665892
- Change-Id: I864d2e796786637132e127ef9b418c0a76c74d6e
-
-commit 69740e62be3800fc918648009645f7a8e52cb73d
-Merge: 2d7bfc878 979da64d8
-Author: TreeHugger Robot <[email protected]>
-Date: Mon Oct 12 20:53:29 2020 +0000
-
- Merge "Add app start source info of apps launched from launcher" into ub-launcher3-master
-
-commit 2d7bfc8782e9ed01178672aeb09ba2a6a07f4f4c
-Author: Jon Miranda <[email protected]>
-Date: Mon Oct 12 12:09:22 2020 -0700
-
- Fix shadowRadius not being used in swipe up animation.
-
- Bug: 168608912
- Change-Id: I08f7bb057237e5061d5f1fc29afb488b204ee385
-
-commit a433fe1fb34715efb38ed094f39da49fce8cd51e
-Merge: 2de606fe7 0471b9836
-Author: Sunny Goyal <[email protected]>
-Date: Mon Oct 12 18:08:35 2020 +0000
-
- Merge "Using FrameCallbacks instead of windowCallbacks for surface removal" into ub-launcher3-master
-
-commit 9a6145efb85f2bbdaccc07166a55e22c15fe27db
-Author: Samuel Fufa <[email protected]>
-Date: Mon Oct 12 09:33:00 2020 -0700
-
- Introduce shortcut container for hotseat event reporting
-
- Bug: 170636685
- Test: Manual
- Change-Id: I5abeb17976bbafdc8cc74fb8b9a586d544c682fc
-
-commit 2de606fe731573c081fd2d6ba166e21ea6aa2e9c
-Author: Yogisha Dixit <[email protected]>
-Date: Mon Oct 12 15:36:07 2020 +0100
-
- Delete the minimal database to force refresh.
-
- Bug: 169771796
- Test: manual
- Change-Id: Ic2188bb162f295c208346861fddc137ace19ddcb
-
-commit 0471b9836c9e382dc14bdc3abdf8502fb2b9f266
-Author: Sunny Goyal <[email protected]>
-Date: Wed Sep 23 13:54:37 2020 -0700
-
- Using FrameCallbacks instead of windowCallbacks for surface removal
-
- WindowCallbacks is called during the draw pass, before the frame has
- been sent to the surfaceFlinger. Frame callback will provide a closer
- approximation for when the frame is actually rendered on screen.
-
- Bug: 141126144
- Change-Id: I62aab526c2ca24b00b5e7b312b36080f26c7b439
-
-commit 2727434c44d06882925369bf4b43687a06be4a3f
-Merge: 59f532fe9 1b9e199b3
-Author: Schneider Victor-tulias <[email protected]>
-Date: Fri Oct 9 20:09:08 2020 +0000
-
- Merge "Fix hotseat and prediction row to allow updates when empty." into ub-launcher3-master
-
-commit 59f532fe9e2b1817c094641f3c7c517f42e4faf0
-Merge: d2bfce71f b5334e3f0
-Author: TreeHugger Robot <[email protected]>
-Date: Fri Oct 9 19:52:54 2020 +0000
-
- Merge "Improve search section header" into ub-launcher3-master
-
-commit 979da64d8254599c332d83bf94f3f1fc3fe45fef
-Author: Riddle Hsu <[email protected]>
-Date: Tue Sep 22 21:52:40 2020 +0800
-
- Add app start source info of apps launched from launcher
-
- Bug: 166614700
- Test: Enable statsd log: "adb shell cmd stats print-logs"
- adb logcat | grep statsd | grep "(48)"
- The line may contain 0x100000->1[I] 0x110000->10[I]
- that means 1=from launcher and 10=latency 10ms.
- Change-Id: Iddaff7066b66e241ba58ec87129ddbe2c531dc7e
- (cherry picked from commit 7bdf3574a3bff06a377b4364877687bfa7619d06)
-
-commit d2bfce71f776fd05633dfd915dfc664309274677
-Merge: ed4530fed 222afb970
-Author: Winson Chung <[email protected]>
-Date: Fri Oct 9 16:39:06 2020 +0000
-
- Merge "Comply with the ISystemUiProxy.aidl change" into ub-launcher3-master
-
-commit ed4530fedda0bf876f91d0745fc70d0f30d42991
-Merge: 692d2109a 9d4a96ed0
-Author: Winson Chung <[email protected]>
-Date: Fri Oct 9 16:39:06 2020 +0000
-
- Merge "Add latency metrics for recents gesture" into ub-launcher3-master
-
-commit 1b9e199b3d9c81c793758d96bb03e0c51c1b3fb1
-Author: Schneider Victor-tulias <[email protected]>
-Date: Thu Oct 8 15:50:22 2020 -0400
-
- Fix hotseat and prediction row to allow updates when empty.
-
- Rotating the screen in the homescreen empties the hotseat, however it does not get populated while it is visible to the user. The user should not be able to see an empty hotseat or prediction row if predictions are available. It should therefore be possible to populate these when they are empty even if they are visible to the user.
-
- Change-Id: I8e5252bd29050c2cd9d443aedcb3f3e305c0e2d7
-
-commit b5334e3f07f0561808a2d6e9bba55f1e3a89191e
-Author: Hyunyoung Song <[email protected]>
-Date: Fri Oct 9 00:50:48 2020 -0700
-
- Improve search section header
-
- Change-Id: I47cf207f0d0ab792c0e7a47c9d1185eec087ec88
-
-commit 692d2109a6702706d24b3b819d115882f7362509
-Author: Samuel Fufa <[email protected]>
-Date: Thu Oct 8 18:42:48 2020 -0700
-
- invalidate itemDecoration on predictedRow focus draw
-
- Change-Id: I66c731f00ae1c1292c51ff281957f05fd2d70dfa
-
-commit 8d5b118060bff7f7518a9a14c0be5d265621f14c
-Author: Samuel Fufa <[email protected]>
-Date: Thu Oct 8 13:11:25 2020 -0700
-
- Revert PredictionRow shuoldDraw check
-
- + Show Rounded play result icons
-
- Bug: 168805872
- Test: Manual
- Change-Id: I663c7f7ca1f1ac072e5e9c441deabef7c3fbd97b
-
-commit 86f8df6cf954ac27ab092b9ef8a4db3c9979c4cb
-Merge: 4d19854b2 16045060c
-Author: Hilary Huo <[email protected]>
-Date: Thu Oct 8 18:43:51 2020 +0000
-
- Merge "[pixel-search] add escape hatch" into ub-launcher3-master
-
-commit 4d19854b25a54599fe9b0ac8be9d60cf6c21d7ba
-Merge: 0827e1e32 ab9ad20be
-Author: Samuel Fufa <[email protected]>
-Date: Thu Oct 8 18:40:56 2020 +0000
-
- Merge "Search UI cleanup" into ub-launcher3-master
-
-commit 16045060c35639aea85afc572bea768d16e6c9f9
-Author: Hilary Huo <[email protected]>
-Date: Thu Oct 8 10:18:41 2020 -0700
-
- [pixel-search] add escape hatch
-
- Change-Id: I33ffea1fc0859564955380d7d1db317293d1a2cb
-
-commit 0827e1e32a5f99fa02418dae37270c6db8c989d2
-Merge: 3463f0a87 68d7a6e5b
-Author: Andy Wickham <[email protected]>
-Date: Thu Oct 8 16:53:29 2020 +0000
-
- Merge "Adds feature flag for BC Smartspace." into ub-launcher3-master
-
-commit ab9ad20be600d1cbdc6b54a491d5fbb4c2cf9c16
-Author: Samuel Fufa <[email protected]>
-Date: Wed Oct 7 15:18:24 2020 -0700
-
- Search UI cleanup
-
- - offset all apps header padding with search input margin
- - avoid check shouldDraw check on HeaderRow. (race condition)
-
- Bug: 170263425
- Change-Id: I11a1fbb448aa6afd18ec0984af9bb8b1d7600f69
-
-commit 68d7a6e5b28af8cc55bdae7efc24cc7ebee81257
-Author: Andy Wickham <[email protected]>
-Date: Wed Oct 7 14:27:17 2020 -0700
-
- Adds feature flag for BC Smartspace.
-
- Change-Id: Iaf9fb7507d0ccd004a4e00188c75dadd6a059246
-
-commit 3463f0a876ff486ce03e160134e0504158271a92
-Merge: 2470d812a 4b7f38b8f
-Author: TreeHugger Robot <[email protected]>
-Date: Wed Oct 7 20:09:04 2020 +0000
-
- Merge "Align fallback result query with result text" into ub-launcher3-master
-
-commit 2470d812a1ae989e67781e5056b534ad9a960819
-Merge: cae7d74d8 7a6e4c931
-Author: Vadim Tryshev <[email protected]>
-Date: Wed Oct 7 20:04:09 2020 +0000
-
- Merge "Annotating Quick Switch CUJ for 3-button mode" into ub-launcher3-master
-
-commit cae7d74d898769727105850ea5473c2c0ae25fdb
-Merge: e9bf2bd14 1fddddb4f
-Author: Tony Wickham <[email protected]>
-Date: Wed Oct 7 18:32:48 2020 +0000
-
- Merge "Update launcher_trace.proto for quick switch" into ub-launcher3-master
-
-commit 7a6e4c931f13b369bfa4328196b4632d6d848a19
-Author: vadimt <[email protected]>
-Date: Tue Oct 6 14:09:16 2020 -0700
-
- Annotating Quick Switch CUJ for 3-button mode
-
- Bug: 169221288
- Change-Id: Ief62345fe6004dde699f44aa0c90329b7cd84e8b
-
-commit 4b7f38b8fa004b514244304fcc07ff514a2fa46b
-Author: Samuel Fufa <[email protected]>
-Date: Tue Oct 6 18:37:46 2020 -0700
-
- Align fallback result query with result text
-
- screenshot: https://screenshot.googleplex.com/6Daj5vdmz2jmznX
- bug: 169438169
- test: Manual
- Change-Id: Ie621ed3c834aec5e9467607da4f685d05d152183
-
-commit 222afb970434c7972589adfc509bd2c256ca6556
-Author: Hongwei Wang <[email protected]>
-Date: Fri Oct 2 13:51:36 2020 -0700
-
- Comply with the ISystemUiProxy.aidl change
-
- Two methods are added to support communications between Launcher and
- SysUI when user swipes an auto PiP-able Activity to home.
-
- Bug: 143965596
- Test: N/A
- Change-Id: I2c73a287a094e882bde3cd71c27f9f66ae20e64a
- (cherry picked from commit 88ddae38db924f700082a113670ce5a719116a95)
-
-commit 9d4a96ed029fdad1e369d5eedd082938f0dc9e01
-Author: Riddle Hsu <[email protected]>
-Date: Wed Sep 30 00:32:04 2020 +0800
-
- Add latency metrics for recents gesture
-
- Pass the touch down time to RecentsAnimation#startRecentsActivity.
-
- Bug: 169221287
- Test: Enable statsd log: "adb shell cmd stats print-logs"
- Touch gesture navigation bar.
- adb logcat | grep statsd | grep "(48)"
- The line may contain 0x100000->4[I] 0x110000->20[I]
- that means 4=by recents and 20=latency 20ms.
- Change-Id: I81ee804895b7712f4d925736f5b4694c11a12cbe
- (cherry picked from commit 63623967b83edad56db58173ebb6687c685b9177)
-
-commit e9bf2bd14c9a7a48f8f93687932d41b1418cf4e4
-Merge: 73ae75474 d028937e7
-Author: Tracy Zhou <[email protected]>
-Date: Wed Oct 7 02:19:04 2020 +0000
-
- Merge "[Live tile] Finish recents animation when the phone goes to sleep in live tile mode" into ub-launcher3-master
-
-commit 1fddddb4f30505e0fc9bb2e7c0d88b38ad900e54
-Author: Tony Wickham <[email protected]>
-Date: Tue Sep 29 17:29:06 2020 -0700
-
- Update launcher_trace.proto for quick switch
-
- Sample output from one entry:
- entry {
- elapsed_realtime_nanos: 440461382888540
- launcher {
- touch_interaction_service {
- service_connected: true
- overview_component_obvserver {
- overview_activity_started: true
- overview_activity_resumed: false
- }
- input_consumer {
- name: "TYPE_OTHER_ACTIVITY:TYPE_ONE_HANDED"
- swipe_handler {
- gesture_state {
- endTarget: NEW_TASK
- }
- is_recents_attached_to_app_window: true
- scroll_offset: 846
- app_to_overview_progress: 0
- }
- }
- }
- }
- }
-
- Bug: 167259591
- Change-Id: I7f199d88f1d736efcea6b9165b8c4b77a5d27c58
-
-commit 73ae75474ec1dd8807d814ea6c22323905d2070c
-Merge: 8a6f3e40d 0ebbc1880
-Author: TreeHugger Robot <[email protected]>
-Date: Tue Oct 6 23:18:44 2020 +0000
-
- Merge "Removing tracing for a gone flake" into ub-launcher3-master
-
-commit 8a6f3e40d0321217c624055db7929c397e455e0c
-Merge: e29a9f796 565ed4ff6
-Author: TreeHugger Robot <[email protected]>
-Date: Tue Oct 6 22:49:40 2020 +0000
-
- Merge "Update Search UI" into ub-launcher3-master
-
-commit 0ebbc18803aaf8ef2f6db7d628d7ae1ce322e842
-Author: vadimt <[email protected]>
-Date: Tue Oct 6 14:52:27 2020 -0700
-
- Removing tracing for a gone flake
-
- Bug: 156044202
- Change-Id: Ice142bb941fee7b731f46c2073fab17d83bbc871
-
-commit 565ed4ff69b534812818a2b9aa8789a1aea210eb
-Author: Samuel Fufa <[email protected]>
-Date: Wed Sep 30 10:42:07 2020 -0700
-
- Update Search UI
-
- [preview attached to bug]
-
- Bug: 169438169
- Test: Manual
- Change-Id: I085f3dd38ac373c1afab82a637ec08715a6e0cc5
-
-commit e29a9f7961e6db0915bc028ef7e871dcb2c8bde0
-Merge: 2c5ed10ff be17bdcd2
-Author: Jayaprakash Sundararaj <[email protected]>
-Date: Tue Oct 6 21:00:20 2020 +0000
-
- Merge "[Search] Add logging to People and badding as to icons." into ub-launcher3-master
-
-commit be17bdcd221f501c45876abe2249c1007858d0c0
-Author: jayaprakashs <[email protected]>
-Date: Mon Oct 5 09:01:52 2020 -0700
-
- [Search] Add logging to People and badding as to icons.
-
- Change-Id: I65948a2faca436216a94aa46139d425b8eade827
-
-commit 2c5ed10ffa1a870de35f9b3c0c558270aff498dd
-Merge: b2b65a1ef 8ed9707cf
-Author: Tracy Zhou <[email protected]>
-Date: Tue Oct 6 18:40:57 2020 +0000
-
- Merge "[Live Tile] Support launching another task (other than the current running task) in Overview" into ub-launcher3-master
-
-commit b2b65a1ef58b020923d112051535b6eb83b582df
-Merge: 3cf264f49 4c14f4b9e
-Author: Samuel Fufa <[email protected]>
-Date: Tue Oct 6 17:45:35 2020 +0000
-
- Merge "Avoid double search item highlight" into ub-launcher3-master
-
-commit 8ed9707cf3a4300cb61942f08f0752c80eed086b
-Author: Tracy Zhou <[email protected]>
-Date: Mon Sep 14 23:25:37 2020 -0700
-
- [Live Tile] Support launching another task (other than the current running task) in Overview
-
- - Get rid of the defer cancelation logic
- - Render animation on the task view of the task being launched upon task view appeared callback
- - Finish the recents animation upon the end of the recents window animation
-
- Fixes: 164926736
- Test: manual
- Change-Id: Ibffb6a9c74c235efc8615a22b0306551532c7b61
-
-commit 3cf264f498e37c482fa4c559bf48ffa791279585
-Author: Schneider Victor-tulias <[email protected]>
-Date: Tue Sep 22 12:58:38 2020 -0700
-
- Prevent hotseat updates if it is visible to the user.
-
- Test: manual
-
- Fixes: 168653219
-
- Changing app icons under the user's finger could be disruptive. Added a checks for whether the hotseatand all apps predictions are visible and callbacks to update them when they become hidden.
-
- Change-Id: Ib9e6e904e9f662ecfaeea6a2fe21d1d81ba39b96
-
-commit b6aff1f56d55a36256446ec3970d92e9da39b98c
-Author: Hyunyoung Song <[email protected]>
-Date: Mon Oct 5 16:08:35 2020 -0700
-
- Fix NPE inside RecentsOrientedState
-
- Bug: 169963211
- Change-Id: I86dd337dc1b862f3fa99b91b47fa250076233f96
-
-commit eab40983b9a48b933bde5ca95a82ebd4d83b233d
-Merge: 83ce7c0b5 020e628f2
-Author: Jonathan Miranda <[email protected]>
-Date: Mon Oct 5 22:20:27 2020 +0000
-
- Merge "Add shadow radius to windows during app launch / close animations." into ub-launcher3-master
-
-commit 83ce7c0b5e461386bb92883a8d6cefe8365cd9ae
-Merge: 679d920bf d6b1f3c08
-Author: TreeHugger Robot <[email protected]>
-Date: Mon Oct 5 19:18:39 2020 +0000
-
- Merge "Action icon should be used as a badge instead of main icon" into ub-launcher3-master
-
-commit 679d920bf5151cffed4e8186c12c25d8d7907af9
-Merge: e108cc609 0c943966d
-Author: TreeHugger Robot <[email protected]>
-Date: Mon Oct 5 19:13:50 2020 +0000
-
- Merge "Add null check for input receiver before updating batching" into ub-launcher3-master
-
-commit e108cc609d0a7fd58f0c7e16ce45fa79be6dd272
-Merge: 470403eb5 f622e42bf
-Author: TreeHugger Robot <[email protected]>
-Date: Mon Oct 5 18:39:58 2020 +0000
-
- Merge "Removing unused proto extensions" into ub-launcher3-master
-
-commit 470403eb58879380e2edac2262dc7f40327b2a15
-Merge: a5130482a 1d7ed30db
-Author: TreeHugger Robot <[email protected]>
-Date: Mon Oct 5 18:29:54 2020 +0000
-
- Merge "Remove widgets that no longer fit the workspace in their current spans." into ub-launcher3-master
-
-commit 4c14f4b9eda8332347c81e0cf51c5de4dbc06399
-Author: Samuel Fufa <[email protected]>
-Date: Mon Oct 5 10:50:00 2020 -0700
-
- Avoid double search item highlight
-
- Change-Id: Ic2e28b18f6d5e3ed32cd5646bc3bb4789c378e57
-
-commit 0c943966d373d8ae7eef2b08e88ac44bf57d8a8d
-Author: Winson Chung <[email protected]>
-Date: Mon Oct 5 10:23:27 2020 -0700
-
- Add null check for input receiver before updating batching
-
- - A change in the system (ie. sysui crash or nav mode change) could
- cause the input monitor to be disposed before the swipe animation
- settles
-
- Bug: 170121063
- Test: Kill sysui while swiping up
-
- Change-Id: I1417b109fecdb98fae6197c7038dbe9307470853
-
-commit a5130482aee1b0592661bc1c6e178a0de7a163da
-Merge: b21819e18 7fcd74abb
-Author: TreeHugger Robot <[email protected]>
-Date: Mon Oct 5 17:14:21 2020 +0000
-
- Merge "Suggest result should launch Bug: 169980192" into ub-launcher3-master
-
-commit d028937e74a9ea6d36e463de4c87ed37283bbdf6
-Author: Tracy Zhou <[email protected]>
-Date: Sat Oct 3 00:36:53 2020 -0700
-
- [Live tile] Finish recents animation when the phone goes to sleep in live tile mode
-
- Fixes: 169988381
- Test: manual
- Change-Id: Ic71d3e6767eadb6854dbd46581bf9d3242c161a4
-
-commit 7fcd74abb399100ac8243be6ca28c09cc8adc8c8
-Author: Hyunyoung Song <[email protected]>
-Date: Fri Oct 2 19:20:11 2020 -0700
-
- Suggest result should launch
- Bug: 169980192
-
- Change-Id: I762245a5cc4740d093c9cb3b44a508e9e3f2b763
-
-commit b21819e181e99504c22c6ca028261a1f2665c6f9
-Merge: 931bce369 a762b0241
-Author: TreeHugger Robot <[email protected]>
-Date: Fri Oct 2 22:07:12 2020 +0000
-
- Merge "Annotating Quick Switch CUJ for non-3-button modes" into ub-launcher3-master
-
-commit a762b02418695f5a1ff2f96586660de8c3610280
-Author: vadimt <[email protected]>
-Date: Fri Oct 2 13:56:28 2020 -0700
-
- Annotating Quick Switch CUJ for non-3-button modes
-
- Bug: 169221288
- Change-Id: I7145a9e28a2f0a789d19d2a0e3d15630c6e50f6a
-
-commit 931bce3697595a214023bc72923dad47a61d5711
-Merge: c935ba6b8 733e3c609
-Author: TreeHugger Robot <[email protected]>
-Date: Fri Oct 2 19:19:50 2020 +0000
-
- Merge "Moving some initializations to the background thread" into ub-launcher3-master
-
-commit c935ba6b8a2ec163533c0b19309dacb6199e6552
-Merge: a4111f250 58804ac52
-Author: Sunny Goyal <[email protected]>
-Date: Fri Oct 2 18:26:06 2020 +0000
-
- Merge "Adding stats log for add item flow" into ub-launcher3-master
-
-commit 733e3c609b7653a36e58747c881458ec00d98df8
-Author: Sunny Goyal <[email protected]>
-Date: Tue Sep 29 10:32:32 2020 -0700
-
- Moving some initializations to the background thread
-
- HandlerThread.getLooper blocks until the thread is ready. Instead
- moving all looper dependency to the new thread itself.
-
- Change-Id: I240e8c56b855a991433a7fe93875059e6dab146b
-
-commit 58804ac5257f45dddbf7a6db35cf8f369ee1e88e
-Author: Sunny Goyal <[email protected]>
-Date: Wed Sep 16 16:27:40 2020 -0700
-
- Adding stats log for add item flow
-
- Bug: 169385783
- Bug: 168167693
- Change-Id: I37395f1b118727f67e0f14c02f945b8213b165c8
-
-commit a4111f250003328d1aef8bbaab59512208ec46cb
-Merge: 8d14dbe04 f6b72c4ad
-Author: Hilary Huo <[email protected]>
-Date: Fri Oct 2 17:41:22 2020 +0000
-
- Merge "[pixel-search] Bug fix: automatically launch screenshot + center&crop remoteaction icon" into ub-launcher3-master
-
-commit f622e42bf6983d3adb95386bfd6375d281f1d4f2
-Author: Sunny Goyal <[email protected]>
-Date: Fri Oct 2 10:35:56 2020 -0700
-
- Removing unused proto extensions
-
- Change-Id: I6d0319c99934dad5176b6f70b895a4ca772ec45f
-
-commit d6b1f3c086f9ac097cd03e1ee898b153478ec11a
-Author: Hyunyoung Song <[email protected]>
-Date: Fri Oct 2 00:26:35 2020 -0700
-
- Action icon should be used as a badge instead of main icon
-
- Bug: 169796517
- Change-Id: I3f07fdc2ae6e1af463701f942c26c3ca5d836ee2
-
-commit f6b72c4ad1d2e082441a64c4d6a5a02ee8a251ca
-Author: Hilary Huo <[email protected]>
-Date: Thu Oct 1 12:26:48 2020 -0700
-
- [pixel-search] Bug fix: automatically launch screenshot + center&crop remoteaction icon
-
- Bug: b/169330678
- Change-Id: Id5f8a0ce6d68f7ed9e4d1ff258ee3772229eb63b
-
-commit 1d7ed30dba4b2c71fc7b0981532a872a13e5aedb
-Author: Jon Miranda <[email protected]>
-Date: Wed Sep 23 12:15:43 2020 -0700
-
- Remove widgets that no longer fit the workspace in their current spans.
-
- This can happen when display size changes.
- We compare span sizes of widget in the db to the min sizes of the widget
- in the current display size. If the widget can no longer fit in its existing
- spans, we remove it.
-
- Also update test widgets to have minWidth/minHeight of 1dp. This ensures that
- the spanX, spanY, min* values remain consistent between different test devices.
-
- Bug: 168818961
- Change-Id: I723372e4582658f78b2f23ced9073cb77977a6b8
-
-commit 020e628f22cc7975beab439c6da26af2f9ebc15b
-Author: Jon Miranda <[email protected]>
-Date: Mon Sep 28 17:01:42 2020 -0700
-
- Add shadow radius to windows during app launch / close animations.
-
- Bug: 168608912
- Change-Id: I2ec50b0b3711c0861659f9c641bbc05fcdeaab45
diff --git a/ext_tests/res/values/overrides.xml b/ext_tests/res/values/overrides.xml
deleted file mode 100644
index 3f071d4..0000000
--- a/ext_tests/res/values/overrides.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="test_information_handler_class" translatable="false">com.android.launcher3.testing.DebugTestInformationHandler</string>
-</resources>
-
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
deleted file mode 100644
index 6e7a82a..0000000
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.testing;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.app.Activity;
-import android.app.Application;
-import android.content.Context;
-import android.os.Binder;
-import android.os.Bundle;
-import android.system.Os;
-
-import androidx.annotation.Keep;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.icons.ClockDrawableWrapper;
-import com.android.launcher3.testing.shared.TestProtocol;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.WeakHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Class to handle requests from tests, including debug ones.
- */
-public class DebugTestInformationHandler extends TestInformationHandler {
- private static Collection<String> sEvents;
- private static Application.ActivityLifecycleCallbacks sActivityLifecycleCallbacks;
- private static final Map<Activity, Boolean> sActivities =
- Collections.synchronizedMap(new WeakHashMap<>());
- private static int sActivitiesCreatedCount = 0;
-
- public DebugTestInformationHandler(Context context) {
- init(context);
- if (sActivityLifecycleCallbacks == null) {
- sActivityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
- @Override
- public void onActivityCreated(Activity activity, Bundle bundle) {
- sActivities.put(activity, true);
- ++sActivitiesCreatedCount;
- }
-
- @Override
- public void onActivityStarted(Activity activity) {
- }
-
- @Override
- public void onActivityResumed(Activity activity) {
- }
-
- @Override
- public void onActivityPaused(Activity activity) {
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
- }
-
- @Override
- public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
- }
-
- @Override
- public void onActivityDestroyed(Activity activity) {
- }
- };
- ((Application) context.getApplicationContext())
- .registerActivityLifecycleCallbacks(sActivityLifecycleCallbacks);
- }
- }
-
- private static void runGcAndFinalizersSync() {
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
-
- final CountDownLatch fence = new CountDownLatch(1);
- createFinalizationObserver(fence);
- try {
- do {
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
- } while (!fence.await(100, TimeUnit.MILLISECONDS));
- } catch (InterruptedException ex) {
- throw new RuntimeException(ex);
- }
- }
-
- // Create the observer in the scope of a method to minimize the chance that
- // it remains live in a DEX/machine register at the point of the fence guard.
- // This must be kept to avoid R8 inlining it.
- @Keep
- private static void createFinalizationObserver(CountDownLatch fence) {
- new Object() {
- @Override
- protected void finalize() throws Throwable {
- try {
- fence.countDown();
- } finally {
- super.finalize();
- }
- }
- };
- }
-
- @Override
- public Bundle call(String method, String arg, @Nullable Bundle extras) {
- final Bundle response = new Bundle();
- switch (method) {
- case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
- return getLauncherUIProperty(Bundle::putInt,
- l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
- }
-
- case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
- TestProtocol.sDebugTracing = true;
- ClockDrawableWrapper.sRunningInTest = true;
- return response;
-
- case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
- TestProtocol.sDebugTracing = false;
- ClockDrawableWrapper.sRunningInTest = false;
- return response;
-
- case TestProtocol.REQUEST_PID: {
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
- return response;
- }
-
- case TestProtocol.REQUEST_FORCE_GC: {
- runGcAndFinalizersSync();
- return response;
- }
-
- case TestProtocol.REQUEST_START_EVENT_LOGGING: {
- sEvents = new ArrayList<>();
- TestLogging.setEventConsumer(
- (sequence, event) -> {
- final Collection<String> events = sEvents;
- if (events != null) {
- synchronized (events) {
- events.add(sequence + '/' + event);
- }
- }
- });
- return response;
- }
-
- case TestProtocol.REQUEST_STOP_EVENT_LOGGING: {
- TestLogging.setEventConsumer(null);
- sEvents = null;
- return response;
- }
-
- case TestProtocol.REQUEST_GET_TEST_EVENTS: {
- if (sEvents == null) {
- // sEvents can be null if Launcher died and restarted after
- // REQUEST_START_EVENT_LOGGING.
- return response;
- }
-
- synchronized (sEvents) {
- response.putStringArrayList(
- TestProtocol.TEST_INFO_RESPONSE_FIELD, new ArrayList<>(sEvents));
- }
- return response;
- }
-
- case TestProtocol.REQUEST_REINITIALIZE_DATA: {
- final long identity = Binder.clearCallingIdentity();
- try {
- MODEL_EXECUTOR.execute(() -> {
- LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
- model.getModelDbController().createEmptyDB();
- MAIN_EXECUTOR.execute(model::forceReload);
- });
- return response;
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- case TestProtocol.REQUEST_CLEAR_DATA: {
- final long identity = Binder.clearCallingIdentity();
- try {
- MODEL_EXECUTOR.execute(() -> {
- LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
- model.getModelDbController().createEmptyDB();
- model.getModelDbController().clearEmptyDbFlag();
- MAIN_EXECUTOR.execute(model::forceReload);
- });
- return response;
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- case TestProtocol.REQUEST_HOTSEAT_ICON_NAMES: {
- return getLauncherUIProperty(Bundle::putStringArrayList, l -> {
- ShortcutAndWidgetContainer hotseatIconsContainer =
- l.getHotseat().getShortcutsAndWidgets();
- ArrayList<String> hotseatIconNames = new ArrayList<>();
-
- for (int i = 0; i < hotseatIconsContainer.getChildCount(); i++) {
- // Use unchecked cast to catch changes in hotseat layout
- BubbleTextView icon = (BubbleTextView) hotseatIconsContainer.getChildAt(i);
- hotseatIconNames.add((String) icon.getText());
- }
-
- return hotseatIconNames;
- });
- }
-
- case TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT: {
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, sActivitiesCreatedCount);
- return response;
- }
-
- case TestProtocol.REQUEST_GET_ACTIVITIES: {
- response.putStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- sActivities.keySet().stream().map(
- a -> a.getClass().getSimpleName() + " ("
- + (a.isDestroyed() ? "destroyed" : "current") + ")")
- .toArray(String[]::new));
- return response;
- }
-
- case TestProtocol.REQUEST_MODEL_QUEUE_CLEARED:
- return getFromExecutorSync(MODEL_EXECUTOR, Bundle::new);
-
- default:
- return super.call(method, arg, extras);
- }
- }
-}
diff --git a/go/quickstep/res/layout/overview_actions_container.xml b/go/quickstep/res/layout/overview_actions_container.xml
index 48650aa..e31f462 100644
--- a/go/quickstep/res/layout/overview_actions_container.xml
+++ b/go/quickstep/res/layout/overview_actions_container.xml
@@ -120,6 +120,19 @@
android:layout_height="1dp"
android:layout_weight="1"
android:visibility="gone" />
+
</LinearLayout>
+ <!-- Unused. Included only for compatibility with parent class. -->
+ <Button
+ android:id="@+id/action_save_app_pair"
+ style="@style/GoOverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center_horizontal"
+ android:drawableStart="@drawable/ic_save_app_pair_up_down"
+ android:text="@string/action_save_app_pair"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor"
+ android:visibility="gone" />
+
</com.android.quickstep.views.GoOverviewActionsView>
\ No newline at end of file
diff --git a/go/quickstep/res/values-be/strings.xml b/go/quickstep/res/values-be/strings.xml
index 83374bb..de8766f 100644
--- a/go/quickstep/res/values-be/strings.xml
+++ b/go/quickstep/res/values-be/strings.xml
@@ -4,7 +4,7 @@
<string name="app_share_drop_target_label" msgid="5804774105974539508">"Абагуліць праграму"</string>
<string name="action_listen" msgid="2370304050784689486">"Праслухаць"</string>
<string name="action_translate" msgid="8028378961867277746">"Перакласці"</string>
- <string name="action_search" msgid="6269564710943755464">"Аб\'ектыў"</string>
+ <string name="action_search" msgid="6269564710943755464">"Аб’ектыў"</string>
<string name="dialog_acknowledge" msgid="2804025517675853172">"ЗРАЗУМЕЛА"</string>
<string name="dialog_cancel" msgid="6464336969134856366">"СКАСАВАЦЬ"</string>
<string name="dialog_settings" msgid="6564397136021186148">"НАЛАДЫ"</string>
diff --git a/go/quickstep/res/values-fa/strings.xml b/go/quickstep/res/values-fa/strings.xml
index 47786e9..8453d4e 100644
--- a/go/quickstep/res/values-fa/strings.xml
+++ b/go/quickstep/res/values-fa/strings.xml
@@ -14,7 +14,7 @@
<string name="assistant_not_selected_text" msgid="3244613673884359276">"برای گوش کردن به نوشتار در صفحهنمایشتان یا ترجمه کردن آن، یکی از برنامههای دستیار دیجیتالی را در «تنظیمات» انتخاب کنید"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"برای استفاده از این ویژگی، دستیارتان را تغییر دهید"</string>
<string name="assistant_not_supported_text" msgid="1708031078549268884">"برای گوش کردن به نوشتار در صفحهنمایشتان یا ترجمه کردن آن، برنامه دستیار دیجیتالیتان را در «تنظیمات» تغییر دهید"</string>
- <string name="tooltip_listen" msgid="7634466447860989102">"برای گوش کردن به نوشتار در این صفحه، اینجا ضربه بزنید"</string>
- <string name="tooltip_translate" msgid="4184845868901542567">"برای ترجمه نوشتار در این صفحه، اینجا ضربه بزنید"</string>
+ <string name="tooltip_listen" msgid="7634466447860989102">"برای گوش کردن به نوشتار در این صفحه، اینجا تکضرب بزنید"</string>
+ <string name="tooltip_translate" msgid="4184845868901542567">"برای ترجمه نوشتار در این صفحه، اینجا تکضرب بزنید"</string>
<string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"نمیتوان این برنامه را همرسانی کرد"</string>
</resources>
diff --git a/go/quickstep/res/values-fr-rCA/strings.xml b/go/quickstep/res/values-fr-rCA/strings.xml
index 2cc9d8f..e48faeb 100644
--- a/go/quickstep/res/values-fr-rCA/strings.xml
+++ b/go/quickstep/res/values-fr-rCA/strings.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_share_drop_target_label" msgid="5804774105974539508">"Partager application"</string>
+ <string name="app_share_drop_target_label" msgid="5804774105974539508">"Partager appli"</string>
<string name="action_listen" msgid="2370304050784689486">"Écouter"</string>
<string name="action_translate" msgid="8028378961867277746">"Traduire"</string>
<string name="action_search" msgid="6269564710943755464">"Lentille"</string>
@@ -9,12 +9,12 @@
<string name="dialog_cancel" msgid="6464336969134856366">"ANNULER"</string>
<string name="dialog_settings" msgid="6564397136021186148">"PARAMÈTRES"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Traduire ou écouter le texte à l\'écran"</string>
- <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Des renseignements comme du texte sur votre écran, des adresses Web et des captures d\'écran peuvent être partagés avec Google.\n\nPour modifier les renseignements que vous partagez, accédez à "<b>"Paramètres > Applications > Applications par défaut > Application d\'assistant numérique"</b>"."</string>
+ <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Des renseignements comme du texte sur votre écran, des adresses Web et des captures d\'écran peuvent être partagés avec Google.\n\nPour modifier les renseignements que vous partagez, accédez à "<b>"Paramètres > Applis > Applis par défaut > Appli d\'assistant numérique"</b>"."</string>
<string name="assistant_not_selected_title" msgid="5017072974603345228">"Choisir un assistant pour utiliser cette fonctionnalité"</string>
- <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pour écouter ou traduire le texte affiché sur votre écran, choisissez l\'application d\'un assistant numérique dans les paramètres"</string>
+ <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pour écouter ou traduire le texte affiché sur votre écran, choisissez l\'appli d\'un assistant numérique dans les paramètres"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"Modifier votre assistant pour utiliser cette fonctionnalité"</string>
- <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pour écouter ou traduire le texte affiché sur votre écran, modifiez l\'application de votre assistant numérique dans les paramètres"</string>
+ <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pour écouter ou traduire le texte affiché sur votre écran, modifiez l\'appli de votre assistant numérique dans les paramètres"</string>
<string name="tooltip_listen" msgid="7634466447860989102">"Touchez ce bouton pour écouter le texte affiché sur cet écran"</string>
<string name="tooltip_translate" msgid="4184845868901542567">"Touchez ce bouton pour traduire le texte affiché sur cet écran"</string>
- <string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Cette application ne peut pas être partagée"</string>
+ <string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Cette appli ne peut pas être partagée"</string>
</resources>
diff --git a/go/quickstep/res/values-nb/strings.xml b/go/quickstep/res/values-nb/strings.xml
index 662b544..6299cc8 100644
--- a/go/quickstep/res/values-nb/strings.xml
+++ b/go/quickstep/res/values-nb/strings.xml
@@ -9,7 +9,7 @@
<string name="dialog_cancel" msgid="6464336969134856366">"AVBRYT"</string>
<string name="dialog_settings" msgid="6564397136021186148">"INNSTILLINGER"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Oversett eller lytt til tekst på skjermen"</string>
- <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informasjon som tekst på skjermen, nettadresser og skjermdumper kan deles med Google.\n\nFor å endre hvilken informasjon du deler, gå til "<b>"Innstillinger > Apper > Standardapper > Digital assistent-app"</b>"."</string>
+ <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informasjon som tekst på skjermen, nettadresser og skjermbilder kan deles med Google.\n\nFor å endre hvilken informasjon du deler, gå til "<b>"Innstillinger > Apper > Standardapper > Digital assistent-app"</b>"."</string>
<string name="assistant_not_selected_title" msgid="5017072974603345228">"Velg en assistent for å bruke denne funksjonen"</string>
<string name="assistant_not_selected_text" msgid="3244613673884359276">"For å høre eller oversette tekst på skjermen, velg en digital assistent-app i innstillingene"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"Endre assistenten for å bruke denne funksjonen"</string>
diff --git a/go/quickstep/res/values-ne/strings.xml b/go/quickstep/res/values-ne/strings.xml
index 11a70dd..e66f063 100644
--- a/go/quickstep/res/values-ne/strings.xml
+++ b/go/quickstep/res/values-ne/strings.xml
@@ -8,7 +8,7 @@
<string name="dialog_acknowledge" msgid="2804025517675853172">"बुझेँ"</string>
<string name="dialog_cancel" msgid="6464336969134856366">"रद्द गर्नुहोस्"</string>
<string name="dialog_settings" msgid="6564397136021186148">"सेटिङ"</string>
- <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"स्क्रिनमा देखिने पाठ अनुवाद गरियोस् वा पढेर सुनाइयोस्"</string>
+ <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"स्क्रिनमा देखिने पाठ अनुवाद गर्नुहोस् वा पढेर सुनाउनुहोस्"</string>
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"तपाईंको स्क्रिनमा देखिने पाठ, वेब ठेगाना र स्क्रिनसटलगायतका जानकारी Google सँग सेयर गर्न सकिन्छ।\n\nकुन कुन जानकारी सेयर गर्न दिने भन्ने सेटिङ बदल्न "<b>"सेटिङ > एप > डिफल्ट एप > डिजिटल सहायक एप"</b>" मा जानुहोस्।"</string>
<string name="assistant_not_selected_title" msgid="5017072974603345228">"तपाईं यो सुविधा चलाउन चाहनुहुन्छ भने कुनै सहायक छनौट गर्नुहोस्"</string>
<string name="assistant_not_selected_text" msgid="3244613673884359276">"तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल सहायक एप छनौट गर्नुहोस्"</string>
diff --git a/go/quickstep/res/values/config.xml b/go/quickstep/res/values/config.xml
index 796d14d..147dd96 100644
--- a/go/quickstep/res/values/config.xml
+++ b/go/quickstep/res/values/config.xml
@@ -21,4 +21,7 @@
<bool name="enable_niu_actions">true</bool>
<string name="task_overlay_factory_class" translatable="false">com.android.quickstep.TaskOverlayFactoryGo</string>
+
+ <!-- String representing the intent to delete a package. -->
+ <string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;B.android.intent.extra.RETURN_RESULT=true;end</string>
</resources>
\ No newline at end of file
diff --git a/go/res/xml/device_profiles.xml b/go/quickstep/res/xml/device_profiles.xml
similarity index 100%
rename from go/res/xml/device_profiles.xml
rename to go/quickstep/res/xml/device_profiles.xml
diff --git a/go/quickstep/src/com/android/launcher3/BuildConfig.java b/go/quickstep/src/com/android/launcher3/BuildConfig.java
new file mode 100644
index 0000000..cfcda39
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/BuildConfig.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+public final class BuildConfig {
+ public static final String APPLICATION_ID = "com.android.launcher3";
+
+ public static final boolean IS_STUDIO_BUILD = false;
+ /**
+ * Flag to state if the QSB is on the first screen and placed on the top,
+ * this can be overwritten in other launchers with a different value, if needed.
+ */
+ public static final boolean QSB_ON_FIRST_SCREEN = true;
+
+ /**
+ * Flag to state if the widget on the top of the first screen should be shown.
+ */
+ public static final boolean WIDGET_ON_FIRST_SCREEN = false;
+
+ /**
+ * Flag to control various developer centric features
+ */
+ public static final boolean IS_DEBUG_DEVICE = false;
+
+ // Flag to control widgets support in Launcher
+ public static final boolean WIDGETS_ENABLED = false;
+ // Flag to control notification dots support in Launcher
+ public static final boolean NOTIFICATION_DOTS_ENABLED = false;
+}
diff --git a/go/quickstep/src/com/android/launcher3/model/AppShareabilityManager.java b/go/quickstep/src/com/android/launcher3/model/AppShareabilityManager.java
index 0d0f700..556d29c 100644
--- a/go/quickstep/src/com/android/launcher3/model/AppShareabilityManager.java
+++ b/go/quickstep/src/com/android/launcher3/model/AppShareabilityManager.java
@@ -35,6 +35,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.launcher3.model.AppShareabilityDatabase.ShareabilityDao;
import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
import java.lang.annotation.Retention;
import java.util.ArrayList;
@@ -47,7 +48,7 @@
* Each app's status is retrieved from the Play Store's API. Statuses are cached in order
* to limit extraneous calls to that API (which can be time-consuming).
*/
-public class AppShareabilityManager {
+public class AppShareabilityManager implements SafeCloseable {
@Retention(SOURCE)
@IntDef({
ShareabilityStatus.UNKNOWN,
@@ -194,6 +195,11 @@
}
}
+ @Override
+ public void close() {
+ mDatabase.close();
+ }
+
/**
* Provides a testable instance of this class
* This instance allows database queries on the main thread
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
index 253147d..26ca06a 100644
--- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -56,7 +56,7 @@
import com.android.quickstep.util.AssistContentRequester;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.GoOverviewActionsView;
-import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -101,8 +101,8 @@
/**
* Create a new overlay instance for the given View
*/
- public TaskOverlayGo createOverlay(TaskThumbnailView thumbnailView) {
- return new TaskOverlayGo(thumbnailView, mContentRequester);
+ public TaskOverlayGo createOverlay(TaskContainer taskContainer) {
+ return new TaskOverlayGo(taskContainer, mContentRequester);
}
/**
@@ -120,9 +120,9 @@
private OverlayDialogGo mDialog;
private ArrowTipView mArrowTipView;
- private TaskOverlayGo(TaskThumbnailView taskThumbnailView,
+ private TaskOverlayGo(TaskContainer taskContainer,
AssistContentRequester assistContentRequester) {
- super(taskThumbnailView);
+ super(taskContainer);
mFactoryContentRequester = assistContentRequester;
mSharedPreferences = LauncherPrefs.getPrefs(mApplicationContext);
}
@@ -148,7 +148,8 @@
// Disable Overview Actions for Work Profile apps
boolean isManagedProfileTask =
UserManager.get(mApplicationContext).isManagedProfile(task.key.userId);
- boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot() && !isManagedProfileTask;
+ boolean isAllowedByPolicy = mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot()
+ && !isManagedProfileTask;
getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
mTaskPackageName = task.key.getPackageName();
mSharedPreferences = LauncherPrefs.getPrefs(mApplicationContext);
@@ -162,8 +163,7 @@
int taskId = task.key.id;
mFactoryContentRequester.requestAssistContent(taskId, this::onAssistContentReceived);
- RecentsOrientedState orientedState =
- mThumbnailView.getTaskView().getRecentsView().getPagedViewOrientedState();
+ RecentsOrientedState orientedState = mTaskContainer.getTaskView().getOrientedState();
boolean isInLandscape = orientedState.getDisplayRotation() != ROTATION_0;
// show tooltips in portrait mode only
diff --git a/go/res/values-v26/bools.xml b/go/res/values-v26/bools.xml
deleted file mode 100644
index 1584734..0000000
--- a/go/res/values-v26/bools.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2017, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<resources>
- <bool name="notification_dots_enabled">false</bool>
-</resources>
-
diff --git a/go/res/values/override.xml b/go/res/values/override.xml
deleted file mode 100644
index 268cb98..0000000
--- a/go/res/values/override.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
--->
-<resources>
- <!-- String representing the intent to delete a package. -->
- <string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;B.android.intent.extra.RETURN_RESULT=true;end</string>
-</resources>
\ No newline at end of file
diff --git a/go/src/com/android/launcher3/model/LauncherBinder.java b/go/src/com/android/launcher3/model/LauncherBinder.java
deleted file mode 100644
index 7a0dce8..0000000
--- a/go/src/com/android/launcher3/model/LauncherBinder.java
+++ /dev/null
@@ -1,45 +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.model;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-
-/**
- * Binds the results of {@link com.android.launcher3.model.LoaderTask} to the Callbacks objects.
- */
-public class LauncherBinder extends BaseLauncherBinder {
-
- public LauncherBinder(LauncherAppState app, BgDataModel dataModel,
- AllAppsList allAppsList, Callbacks[] callbacks) {
- super(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
- }
-
- @Override
- public void bindDeepShortcuts() {
- }
-
- @Override
- public void bindWidgets() {
- }
-
- @Override
- public void bindSmartspaceWidget() {
- }
-}
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
deleted file mode 100644
index 3a28444..0000000
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ /dev/null
@@ -1,105 +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.model;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.ComponentWithLabelAndIcon;
-import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Predicate;
-
-/**
- * Widgets data model that is used by the adapters of the widget views and controllers.
- *
- * <p> The widgets and shortcuts are organized using package name as its index.
- */
-public class WidgetsModel {
-
- // True is the widget support is disabled.
- public static final boolean GO_DISABLE_WIDGETS = true;
- public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true;
-
- private static final ArrayList<WidgetsListBaseEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
-
- /**
- * Returns a list of {@link WidgetsListBaseEntry} filtered using given widget item filter. All
- * {@link WidgetItem}s in a single row are sorted (based on label and user), but the overall
- * list of {@link WidgetsListBaseEntry}s is not sorted.
- *
- * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
- */
- public synchronized ArrayList<WidgetsListBaseEntry> getFilteredWidgetsListForPicker(
- Context context,
- Predicate<WidgetItem> widgetItemFilter) {
- return EMPTY_WIDGET_LIST;
- }
-
- /**
- * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row are
- * sorted (based on label and user), but the overall list of {@link WidgetsListBaseEntry}s is
- * not sorted. This list is sorted at the UI when using
- * {@link com.android.launcher3.widget.picker.WidgetsDiffReporter}
- *
- * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
- */
- public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) {
- return EMPTY_WIDGET_LIST;
- }
-
- /** Returns a mapping of packages to their widgets without static shortcuts. */
- public synchronized Map<PackageUserKey, List<WidgetItem>> getAllWidgetsWithoutShortcuts() {
- return Map.of();
- }
-
- /**
- * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
- * only widgets and shortcuts associated with the package/user are.
- */
- public List<ComponentWithLabelAndIcon> update(LauncherAppState app,
- @Nullable PackageUserKey packageUser) {
- return Collections.emptyList();
- }
-
-
- public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
- LauncherAppState app) {
- }
-
- public WidgetItem getWidgetProviderInfoByProviderName(
- ComponentName providerName, UserHandle user) {
- return null;
- }
-
- /** Returns {@link PackageItemInfo} of a pending widget. */
- public static PackageItemInfo newPendingItemInfo(
- Context context, ComponentName provider, UserHandle userHandle) {
- return new PackageItemInfo(provider.getPackageName(), userHandle);
- }
-}
diff --git a/go/src/com/android/launcher3/util/AbsGridOccupancy.java b/go/src/com/android/launcher3/util/AbsGridOccupancy.java
deleted file mode 100644
index 4a46bd1..0000000
--- a/go/src/com/android/launcher3/util/AbsGridOccupancy.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-/**
- * Defines method to find the next vacant cell on a grid.
- * This uses the default top-down, left-right approach and can be over-written through
- * code swaps in different launchers.
- */
-public abstract class AbsGridOccupancy {
-
- /**
- * Find the first vacant cell, if there is one.
- *
- * @param vacantOut Holds the x and y coordinate of the vacant cell
- * @param spanX Horizontal cell span.
- * @param spanY Vertical cell span.
- *
- * @return true if a vacant cell was found
- */
- protected boolean findVacantCell(int[] vacantOut, boolean[][] cells, int countX, int countY,
- int spanX, int spanY) {
- for (int y = 0; (y + spanY) <= countY; y++) {
- for (int x = 0; (x + spanX) <= countX; x++) {
- boolean available = !cells[x][y];
- out:
- for (int i = x; i < x + spanX; i++) {
- for (int j = y; j < y + spanY; j++) {
- available = available && !cells[i][j];
- if (!available) break out;
- }
- }
- if (available) {
- vacantOut[0] = x;
- vacantOut[1] = y;
- return true;
- }
- }
- }
- return false;
- }
-}
diff --git a/gradle.properties b/gradle.properties
deleted file mode 100644
index d5c1d77..0000000
--- a/gradle.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# Until all the dependencies move to android X
-android.useAndroidX = true
-android.enableJetifier = true
-org.gradle.parallel=true
-
-ANDROID_X_VERSION=1+
-
-GRADLE_CLASS_PATH=com.android.tools.build:gradle:3.5.1
-
-PROTOBUF_CLASS_PATH=com.google.protobuf:protobuf-gradle-plugin:0.8.8
-PROTOBUF_DEPENDENCY=com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7
-
-BUILD_TOOLS_VERSION=28.0.3
-COMPILE_SDK=android-S
diff --git a/lint-baseline.xml b/lint-baseline.xml
index fe005ca..2ee9531 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -3,13 +3,13 @@
<issue
id="NewApi"
- message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`"
- errorLine1=" return mContext.getResources().getFloat(resId);"
- errorLine2=" ~~~~~~~~">
+ message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)"
+ errorLine1=' <corners android:radius="@android:dimen/system_app_widget_background_radius" />'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/apps/Launcher3/src/com/android/launcher3/util/DynamicResource.java"
- line="73"
- column="40"/>
+ file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
+ line="20"
+ column="14"/>
</issue>
<issue
@@ -34,136 +34,4 @@
column="18"/>
</issue>
- <issue
- id="NewApi"
- message="Call requires API level 28 (current min is 26): `android.app.Person#getKey`"
- errorLine1=" return people.stream().filter(person -> person.getKey() != null)"
- errorLine2=" ~~~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
- line="72"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Method reference requires API level 28 (current min is 26): `Person::getKey`"
- errorLine1=" .map(Person::getKey).sorted().toArray(String[]::new);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
- line="73"
- column="22"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`"
- errorLine1=" AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
- line="1814"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`"
- errorLine1=" : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
- line="1815"
- column="19"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`"
- errorLine1=" AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
- line="1823"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`"
- errorLine1=" : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
- line="1824"
- column="19"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 30 (current min is 26): `android.graphics.Outline#setPath`"
- errorLine1=" outline.setPath(mPath);"
- errorLine2=" ~~~~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/popup/RoundedArrowDrawable.java"
- line="114"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 28 (current min is 26): `android.appwidget.AppWidgetProviderInfo#widgetFeatures`"
- errorLine1=" int featureFlags = mProviderInfo.widgetFeatures;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetAddFlowHandler.java"
- line="93"
- column="28"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 30 (current min is 26): `android.view.View#getWindowInsetsController`"
- errorLine1=" WindowInsetsController insetsController = getWindowInsetsController();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
- line="820"
- column="51"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 30 (current min is 26): `android.view.WindowInsets.Type#ime`"
- errorLine1=" insetsController.hide(WindowInsets.Type.ime());"
- errorLine2=" ~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
- line="822"
- column="53"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 30 (current min is 26): `android.view.WindowInsetsController#hide`"
- errorLine1=" insetsController.hide(WindowInsets.Type.ime());"
- errorLine2=" ~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
- line="822"
- column="30"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Method reference requires API level 28 (current min is 26): `Person::getKey`"
- errorLine1=" : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/src/com/android/launcher3/model/data/WorkspaceItemInfo.java"
- line="194"
- column="42"/>
- </issue>
-
</issues>
\ No newline at end of file
diff --git a/lint-baseline2.xml b/lint-baseline2.xml
deleted file mode 100644
index 84f1b15..0000000
--- a/lint-baseline2.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
-
- <issue
- id="NewApi"
- message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
- errorLine1=' android:topLeftRadius="?android:attr/dialogCornerRadius"'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
- line="6"
- column="9"/>
- </issue>
-
- <issue
- id="NewApi"
- message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
- errorLine1=' android:topRightRadius="?android:attr/dialogCornerRadius" />'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
- line="7"
- column="9"/>
- </issue>
-
- <issue
- id="NewApi"
- message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)"
- errorLine1=' <corners android:radius="@android:dimen/system_app_widget_background_radius" />'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
- line="20"
- column="14"/>
- </issue>
-
-</issues>
\ No newline at end of file
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 7c648b6..823c821 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -121,6 +121,20 @@
}
message TaskSwitcherContainer {
+ /**
+ * Indicates the current OrientationHandler in use in Overview.
+ * In fake landscape, the value will be
+ * {@link com.android.quickstep.orientation.LandscapePagedViewHandler} and in real landscape,
+ * the value will be {@link com.android.quickstep.orientation.PortraitPagedViewHandler} for
+ * example.
+ */
+ optional OrientationHandler orientation_handler = 1;
+
+ enum OrientationHandler {
+ PORTRAIT = 0;
+ LANDSCAPE = 1;
+ SEASCAPE = 2;
+ }
}
// Container for taskbar.
diff --git a/quickstep/Android.bp b/quickstep/Android.bp
index ec4f6fc..f14cebd 100644
--- a/quickstep/Android.bp
+++ b/quickstep/Android.bp
@@ -23,15 +23,23 @@
}
filegroup {
- name: "launcher3-quickstep-robolectric-src",
- path: "robolectric_tests",
- srcs: ["robolectric_tests/src/**/*.java"],
+ name: "launcher3-quickstep-robo-src",
+ path: "tests/multivalentTests",
+ srcs: [
+ "tests/multivalentTests/src/**/*.java",
+ "tests/multivalentTests/src/**/*.kt",
+ ],
}
filegroup {
name: "launcher3-quickstep-tests-src",
path: "tests",
- srcs: ["tests/src/**/*.java", "tests/src/**/*.kt"],
+ srcs: [
+ "tests/multivalentTests/src/**/*.java",
+ "tests/multivalentTests/src/**/*.kt",
+ "tests/src/**/*.java",
+ "tests/src/**/*.kt",
+ ],
}
filegroup {
@@ -44,5 +52,6 @@
"tests/src/com/android/quickstep/TaplOverviewIconTest.java",
"tests/src/com/android/quickstep/TaplTestsQuickstep.java",
"tests/src/com/android/quickstep/TaplTestsSplitscreen.java",
- ]
+ "tests/src/com/android/launcher3/testcomponent/ExcludeFromRecentsTestActivity.java"
+ ],
}
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index e75812a..bf198b6 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -50,7 +50,7 @@
<uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES_FULL" />
<!-- Permission required to start a WidgetPickerActivity. -->
- <permission android:name="${packageName}.permission.START_WIDGET_PICKER_ACTIVITY"
+ <permission android:name="${applicationId}.permission.START_WIDGET_PICKER_ACTIVITY"
android:protectionLevel="signature|privileged" />
<application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
@@ -83,11 +83,12 @@
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
+ android:enableOnBackInvokedCallback="false"
android:taskAffinity=""/>
<!-- Content provider to settings search. The autority should be same as the packageName -->
<provider android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
- android:authorities="${packageName}"
+ android:authorities="${applicationId}"
android:grantUriPermissions="true"
android:multiprocess="true"
android:permission="android.permission.READ_SEARCH_INDEXABLES"
@@ -99,7 +100,7 @@
<!-- FileProvider used for sharing images. -->
<provider android:name="androidx.core.content.FileProvider"
- android:authorities="${packageName}.overview.fileprovider"
+ android:authorities="${applicationId}.overview.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
diff --git a/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
deleted file mode 100644
index 0b17a7b..0000000
--- a/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.testing.DebugTestInformationHandler;
-import com.android.launcher3.testing.shared.TestProtocol;
-
-/**
- * Class to handle requests from tests, including debug ones, to Quickstep Launcher builds.
- */
-public abstract class DebugQuickstepTestInformationHandler extends QuickstepTestInformationHandler {
-
- private final DebugTestInformationHandler mDebugTestInformationHandler;
-
- public DebugQuickstepTestInformationHandler(Context context) {
- super(context);
- mDebugTestInformationHandler = new DebugTestInformationHandler(context);
- }
-
- @Override
- public Bundle call(String method, String arg, @Nullable Bundle extras) {
- Bundle response = new Bundle();
- if (TestProtocol.REQUEST_RECREATE_TASKBAR.equals(method)) {
- // Allow null-pointer to catch illegal states.
- runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
- return response;
- }
- response = super.call(method, arg, extras);
- if (response != null) return response;
- return mDebugTestInformationHandler.call(method, arg, extras);
- }
-}
-
diff --git a/quickstep/res/color/bubblebar_drop_target_bg_color.xml b/quickstep/res/color/bubblebar_drop_target_bg_color.xml
new file mode 100644
index 0000000..ca37c7f
--- /dev/null
+++ b/quickstep/res/color/bubblebar_drop_target_bg_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:alpha="0.35" android:color="?androidprv:attr/materialColorPrimaryContainer" />
+</selector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_bubble_bar_drop_target.xml b/quickstep/res/drawable/bg_bubble_bar_drop_target.xml
new file mode 100644
index 0000000..79e4318
--- /dev/null
+++ b/quickstep/res/drawable/bg_bubble_bar_drop_target.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/bubblebar_drop_target_corner_radius" />
+ <solid android:color="@color/bubblebar_drop_target_bg_color" />
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/materialColorPrimaryContainer" />
+</shape>
diff --git a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
new file mode 100644
index 0000000..d722dd7
--- /dev/null
+++ b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:inset="@dimen/bubble_expanded_view_drop_target_padding">
+ <shape
+ android:shape="rectangle">
+ <corners android:radius="@dimen/bubble_expanded_view_drop_target_corner_radius" />
+ <solid android:color="@color/bubblebar_drop_target_bg_color" />
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/materialColorPrimaryContainer" />
+ </shape>
+</inset>
diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml
index f3ff6ce..143761f 100644
--- a/quickstep/res/drawable/bg_overview_clear_all_button.xml
+++ b/quickstep/res/drawable/bg_overview_clear_all_button.xml
@@ -20,7 +20,7 @@
<item>
<shape android:shape="rectangle"
android:tint="?colorButtonNormal">
- <corners android:radius="24dp" />
+ <corners android:radius="@dimen/recents_clear_all_outline_radius" />
<solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
</shape>
</item>
diff --git a/quickstep/res/drawable/ic_save_app_pair.xml b/quickstep/res/drawable/ic_save_app_pair.xml
deleted file mode 100644
index 4a7ee1a..0000000
--- a/quickstep/res/drawable/ic_save_app_pair.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- Copyright (C) 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:pathData="M13.329,2.305H4.242C2.751,2.305 1.542,3.514 1.542,5.005V13.005C1.542,14.496 2.751,15.705 4.242,15.705H7.875V19.011C7.875,20.502 9.084,21.711 10.575,21.711H19.662C21.153,21.711 22.362,20.502 22.362,19.011V10.011C22.362,8.52 21.153,7.311 19.662,7.311H16.029V5.005C16.029,3.514 14.821,2.305 13.329,2.305ZM14.329,7.311V5.005C14.329,4.452 13.882,4.005 13.329,4.005H4.242C3.69,4.005 3.242,4.452 3.242,5.005V13.005C3.242,13.557 3.69,14.005 4.242,14.005H7.875V10.011C7.875,8.52 9.084,7.311 10.575,7.311H14.329ZM9.575,14.005V10.011C9.575,9.611 9.81,9.266 10.15,9.106C10.285,9.037 10.438,8.999 10.6,8.999H19.687C20.239,8.999 20.687,9.447 20.687,9.999V18.999C20.687,19.399 20.452,19.744 20.113,19.904C19.977,19.972 19.824,20.011 19.662,20.011H10.575C10.023,20.011 9.575,19.563 9.575,19.011V15.705H9.6V14.005H9.575ZM15.542,11.996V14H17.588V15H15.542V16.996H14.542V15H12.464V14H14.542V11.996H15.542Z"
- android:fillColor="#000000"
- android:fillType="evenOdd"/>
-</vector>
diff --git a/quickstep/res/drawable/ic_save_app_pair_left_right.xml b/quickstep/res/drawable/ic_save_app_pair_left_right.xml
new file mode 100644
index 0000000..b104f44
--- /dev/null
+++ b/quickstep/res/drawable/ic_save_app_pair_left_right.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M8.5,4.5H3.5C2.4,4.5 1.5,5.4 1.5,6.5V18.5C1.5,19.6 2.4,20.5 3.5,20.5H8.5C9.6,20.5 10.5,19.6 10.5,18.5V6.5C10.5,5.4 9.6,4.5 8.5,4.5ZM8.5,18.5H3.5V6.5H8.5V18.5ZM14.5,6.5H19.5V13.5H21.5V6.5C21.5,5.4 20.6,4.5 19.5,4.5H14.5C13.4,4.5 12.5,5.4 12.5,6.5V18.5C12.5,19.6 13.4,20.5 14.5,20.5H15.5V18.5H14.5V6.5ZM20.5,14.5V16.5H22.5V18.5H20.5V20.5H18.5V18.5H16.5V16.5H18.5V14.5H20.5Z"
+ android:fillColor="#48473A"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/quickstep/res/drawable/ic_save_app_pair_up_down.xml b/quickstep/res/drawable/ic_save_app_pair_up_down.xml
new file mode 100644
index 0000000..86f110c
--- /dev/null
+++ b/quickstep/res/drawable/ic_save_app_pair_up_down.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M18,2L6,2C4.9,2 4,2.9 4,4L4,9C4,10.1 4.9,11 6,11L18,11C19.1,11 20,10.1 20,9L20,4C20,2.9 19.1,2 18,2ZM18,9L6,9L6,4L18,4L18,9ZM18,13L6,13C4.9,13 4,13.9 4,15L4,20C4,21.1 4.9,22 6,22L13,22L13,20L6,20L6,15L18,15L18,16L20,16L20,15C20,13.9 19.1,13 18,13ZM16,17L18,17L18,19L20,19L20,21L18,21L18,23L16,23L16,21L14,21L14,19L16,19L16,17Z"
+ android:fillColor="#48473A"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/quickstep/res/drawable/keyboard_quick_switch_thumbnail_background.xml b/quickstep/res/drawable/keyboard_quick_switch_thumbnail_background.xml
new file mode 100644
index 0000000..961f5aa
--- /dev/null
+++ b/quickstep/res/drawable/keyboard_quick_switch_thumbnail_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
+</shape>
diff --git a/quickstep/res/layout/bubble_bar_drop_target.xml b/quickstep/res/layout/bubble_bar_drop_target.xml
new file mode 100644
index 0000000..23f240c
--- /dev/null
+++ b/quickstep/res/layout/bubble_bar_drop_target.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/bubblebar_size"
+ android:layout_height="@dimen/bubblebar_size"
+ android:background="@drawable/bg_bubble_bar_drop_target"
+ android:elevation="@dimen/bubblebar_elevation" />
\ No newline at end of file
diff --git a/quickstep/res/layout/bubble_expanded_view_drop_target.xml b/quickstep/res/layout/bubble_expanded_view_drop_target.xml
new file mode 100644
index 0000000..3bd5d31
--- /dev/null
+++ b/quickstep/res/layout/bubble_expanded_view_drop_target.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- TODO(b/330585402): replace 600dp height with calculated value -->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/bubble_expanded_view_drop_target_default_width"
+ android:layout_height="@dimen/bubble_expanded_view_drop_target_default_height"
+ android:layout_margin="@dimen/bubble_expanded_view_drop_target_margin"
+ android:background="@drawable/bg_bubble_expanded_view_drop_target"
+ android:elevation="@dimen/bubblebar_elevation" />
\ No newline at end of file
diff --git a/quickstep/res/layout/digital_wellbeing_toast.xml b/quickstep/res/layout/digital_wellbeing_toast.xml
index 42cddbf..9144c7f 100644
--- a/quickstep/res/layout/digital_wellbeing_toast.xml
+++ b/quickstep/res/layout/digital_wellbeing_toast.xml
@@ -19,7 +19,7 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
style="@style/TextTitle"
android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_height="@dimen/digital_wellbeing_toast_height"
android:background="@drawable/bg_wellbeing_toast"
android:forceHasOverlappingRendering="false"
android:gravity="center"
diff --git a/quickstep/res/layout/floating_desktop_app_select.xml b/quickstep/res/layout/floating_desktop_app_select.xml
deleted file mode 100644
index 375fc44..0000000
--- a/quickstep/res/layout/floating_desktop_app_select.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- Copyright (C) 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<com.android.quickstep.views.DesktopAppSelectView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/desktop_mode_floating_app_select_height"
- android:layout_gravity="top|center_horizontal"
- android:background="@drawable/bg_floating_desktop_select"
- android:elevation="@dimen/desktop_mode_floating_app_select_elevation"
- android:gravity="center_vertical"
- android:orientation="horizontal">
-
- <TextView
- android:id="@+id/desktop_app_select_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/desktop_mode_floating_app_select_text_margin"
- android:layout_marginStart="@dimen/desktop_mode_floating_app_select_margin"
- android:drawablePadding="@dimen/desktop_mode_floating_app_select_text_margin"
- android:drawableStart="@drawable/ic_desktop"
- android:drawableTint="?androidprv:attr/materialColorOnPrimaryContainer"
- android:fontFamily="google-sans-medium"
- android:gravity="center_vertical"
- android:text="@string/desktop_select_app_toast"
- android:textColor="?androidprv:attr/materialColorOnPrimaryContainer"
- android:textSize="@dimen/desktop_mode_floating_app_select_text_size" />
-
- <Button
- android:id="@+id/close_button"
- style="@android:style/Widget.DeviceDefault.Button.Borderless"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/desktop_mode_floating_app_select_margin"
- android:minWidth="0dp"
- android:fontFamily="google-sans-medium"
- android:text="@string/desktop_button_close_app_toast"
- android:textAllCaps="false"
- android:textColor="?androidprv:attr/materialColorPrimary"
- android:textSize="@dimen/desktop_mode_floating_app_select_text_size" />
-
-</com.android.quickstep.views.DesktopAppSelectView>
diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview_thumbnail.xml b/quickstep/res/layout/keyboard_quick_switch_taskview_thumbnail.xml
index dde9cac..8cd8560 100644
--- a/quickstep/res/layout/keyboard_quick_switch_taskview_thumbnail.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_taskview_thumbnail.xml
@@ -18,6 +18,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
- android:background="@drawable/keyboard_quick_switch_task_view_background"
+ android:background="@drawable/keyboard_quick_switch_thumbnail_background"
android:clipToOutline="true"
android:importantForAccessibility="no"/>
diff --git a/quickstep/res/layout/keyboard_quick_switch_overview.xml b/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
similarity index 97%
rename from quickstep/res/layout/keyboard_quick_switch_overview.xml
rename to quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
index 30ca32d..e48794e 100644
--- a/quickstep/res/layout/keyboard_quick_switch_overview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
@@ -40,7 +40,6 @@
android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
android:layout_marginBottom="8dp"
- android:src="@drawable/view_carousel"
android:tint="?androidprv:attr/materialColorOnSurface"
app:layout_constraintVertical_chainStyle="packed"
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index 0fda0bf..fcd2e54 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -21,16 +21,11 @@
<LinearLayout
android:id="@+id/action_buttons"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/overview_actions_height"
android:layout_gravity="bottom|center_horizontal"
android:orientation="horizontal">
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" />
-
<Button
android:id="@+id/action_screenshot"
style="@style/OverviewActionButton"
@@ -40,32 +35,28 @@
android:text="@string/action_screenshot"
android:theme="@style/ThemeControlHighlightWorkspaceColor" />
- <Space
- android:id="@+id/action_split_space"
- android:layout_width="@dimen/overview_actions_button_spacing"
- android:layout_height="1dp"
- android:visibility="gone" />
-
<Button
android:id="@+id/action_split"
style="@style/OverviewActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/overview_actions_button_spacing"
android:text="@string/action_split"
android:theme="@style/ThemeControlHighlightWorkspaceColor"
android:visibility="gone" />
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" />
-
- <Space
- android:id="@+id/oav_three_button_space"
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1"
- android:visibility="gone" />
</LinearLayout>
+ <!-- Currently, the only "group action button" is this save app pair button. If more are added,
+ a new LinearLayout may be needed to contain them, but beware of increased memory usage. -->
+ <Button
+ android:id="@+id/action_save_app_pair"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/action_save_app_pair"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor"
+ android:layout_gravity="bottom|center_horizontal"
+ android:visibility="gone" />
+
</com.android.quickstep.views.OverviewActionsView>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index da94c3a..3380ea4 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -17,10 +17,12 @@
<com.android.quickstep.views.ClearAllButton
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
style="@style/OverviewClearAllButton"
android:id="@+id/clear_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/recents_clear_all"
android:textColor="?androidprv:attr/materialColorOnSurface"
+ launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
android:textSize="14sp" />
\ No newline at end of file
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
index a1bcad0..b004dfd 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
@@ -134,11 +134,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
+ android:layout_above="@id/gesture_tutorial_fragment_action_button"
android:layout_centerHorizontal="true"
android:background="@android:color/transparent"
- android:paddingEnd="24dp"
- android:paddingStart="24dp"
- android:paddingTop="24dp">
+ android:paddingTop="24dp"
+ android:paddingHorizontal="24dp"
+ android:layout_marginBottom="16dp">
<TextView
android:id="@+id/gesture_tutorial_fragment_feedback_title"
@@ -169,7 +170,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- app:layout_constraintBottom_toBottomOf="@id/gesture_tutorial_fragment_action_button"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -193,7 +194,7 @@
android:id="@+id/checkmark_animation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="44dp"
+ android:layout_marginBottom="28dp"
android:gravity="center"
android:scaleType="centerCrop"
app:lottie_loop="false"
@@ -204,19 +205,6 @@
app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_fragment_feedback_subtitle"
app:layout_constraintBottom_toBottomOf="parent" />
- <Button
- android:id="@+id/gesture_tutorial_fragment_action_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@drawable/gesture_tutorial_action_button_background"
- android:stateListAnimator="@null"
- android:text="@string/gesture_tutorial_action_button_label"
- android:visibility="invisible"
- android:layout_marginBottom="60dp"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/checkmark_animation" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -228,4 +216,18 @@
android:src="@drawable/gesture_tutorial_finger_dot"
android:visibility="gone" />
+ <Button
+ android:id="@+id/gesture_tutorial_fragment_action_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/gesture_tutorial_done_button_end_margin"
+ android:layout_marginBottom="@dimen/gesture_tutorial_done_button_bottom_margin"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:background="@drawable/gesture_tutorial_action_button_background"
+ android:stateListAnimator="@null"
+ android:text="@string/gesture_tutorial_action_button_label"
+ android:visibility="invisible"
+ />
+
</com.android.quickstep.interaction.RootSandboxLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/split_instructions_view.xml b/quickstep/res/layout/split_instructions_view.xml
index 1115ff2..797ea45 100644
--- a/quickstep/res/layout/split_instructions_view.xml
+++ b/quickstep/res/layout/split_instructions_view.xml
@@ -29,6 +29,7 @@
android:id="@+id/split_instructions_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
+ android:maxWidth="@dimen/split_instructions_view_max_width"
android:textColor="?androidprv:attr/textColorOnAccent"
android:text="@string/toast_split_select_app" />
@@ -36,6 +37,7 @@
android:id="@+id/split_instructions_text_cancel"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
android:textColor="?androidprv:attr/textColorOnAccent"
android:layout_marginStart="@dimen/split_instructions_start_margin_cancel"
android:text="@string/toast_split_select_app_cancel"
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 9d599c9..cc3b30e 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -28,10 +28,7 @@
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
- <com.android.quickstep.views.TaskThumbnailView
- android:id="@+id/snapshot"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ <include layout="@layout/task_thumbnail" />
<!-- Filtering affects only alpha instead of the visibility since visibility can be altered
separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index 3cafcfd..89e9b3d 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -42,10 +42,7 @@
views that do not inherint from TaskView only or create a generic TaskView that have
N number of tasks.
-->
- <com.android.quickstep.views.TaskThumbnailView
- android:id="@+id/snapshot"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ <include layout="@layout/task_thumbnail"
android:visibility="gone" />
<ViewStub
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index e91e773..87a0f70 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -33,15 +33,10 @@
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
- <com.android.quickstep.views.TaskThumbnailView
- android:id="@+id/snapshot"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ <include layout="@layout/task_thumbnail"/>
- <com.android.quickstep.views.TaskThumbnailView
- android:id="@+id/bottomright_snapshot"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ <include layout="@layout/task_thumbnail"
+ android:id="@+id/bottomright_snapshot" />
<!-- Filtering affects only alpha instead of the visibility since visibility can be altered
separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_menu_with_arrow.xml b/quickstep/res/layout/task_menu_with_arrow.xml
index 38573fd..88e5cf7 100644
--- a/quickstep/res/layout/task_menu_with_arrow.xml
+++ b/quickstep/res/layout/task_menu_with_arrow.xml
@@ -18,16 +18,23 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minWidth="@dimen/task_menu_width_grid"
android:animateLayoutChanges="true"
android:background="@drawable/task_menu_bg"
android:orientation="vertical"
android:visibility="invisible">
- <LinearLayout
- android:id="@+id/menu_option_layout"
+ <ScrollView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:showDividers="middle" />
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:id="@+id/menu_option_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:showDividers="middle" />
+
+ </ScrollView>
</com.android.quickstep.views.TaskMenuViewWithArrow>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
new file mode 100644
index 0000000..f1a3d62
--- /dev/null
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.quickstep.views.TaskThumbnailViewDeprecated
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/snapshot"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task_view_menu_option.xml b/quickstep/res/layout/task_view_menu_option.xml
index 30ab4b1..ffe2401 100644
--- a/quickstep/res/layout/task_view_menu_option.xml
+++ b/quickstep/res/layout/task_view_menu_option.xml
@@ -41,6 +41,8 @@
android:layout_marginStart="@dimen/task_menu_option_text_start_margin"
android:textSize="14sp"
android:textColor="?androidprv:attr/materialColorOnSurface"
- android:focusable="false" />
+ android:focusable="false"
+ android:gravity="start"
+ android:ellipsize="end" />
</LinearLayout>
diff --git a/quickstep/res/layout/taskbar_all_apps_button.xml b/quickstep/res/layout/taskbar_all_apps_button.xml
index c50db2e..94596cb 100644
--- a/quickstep/res/layout/taskbar_all_apps_button.xml
+++ b/quickstep/res/layout/taskbar_all_apps_button.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,10 +14,9 @@
-->
<!-- Note: The actual size will match the taskbar icon sizes in TaskbarView#onLayout(). -->
-<com.android.launcher3.views.IconButtonView
- xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.views.IconButtonView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/BaseIcon.Workspace.Taskbar"
android:layout_width="@dimen/taskbar_icon_min_touch_size"
android:layout_height="@dimen/taskbar_icon_min_touch_size"
- android:contentDescription="@string/all_apps_button_label"
android:backgroundTint="@android:color/transparent"
- />
+ android:contentDescription="@string/all_apps_button_label" />
diff --git a/quickstep/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml
index 0a92fa9..330f85f 100644
--- a/quickstep/res/layout/taskbar_divider.xml
+++ b/quickstep/res/layout/taskbar_divider.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,9 +12,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.views.IconButtonView
- xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.views.IconButtonView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/BaseIcon.Workspace.Taskbar"
android:layout_width="@dimen/taskbar_icon_min_touch_size"
android:layout_height="@dimen/taskbar_icon_min_touch_size"
- android:contentDescription="@string/taskbar_divider_a11y_title"
- android:backgroundTint="@android:color/transparent" />
\ No newline at end of file
+ android:backgroundTint="@android:color/transparent"
+ android:contentDescription="@string/taskbar_divider_a11y_title" />
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_divider_popup_menu.xml b/quickstep/res/layout/taskbar_divider_popup_menu.xml
index 7f4f76c..b184191 100644
--- a/quickstep/res/layout/taskbar_divider_popup_menu.xml
+++ b/quickstep/res/layout/taskbar_divider_popup_menu.xml
@@ -16,8 +16,9 @@
-->
<com.android.launcher3.taskbar.TaskbarDividerPopupView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/taskbar_pinning_popup_menu_width"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minWidth="@dimen/taskbar_pinning_popup_menu_width"
android:focusable="true"
android:background="@drawable/popup_background"
android:orientation="vertical">
@@ -51,14 +52,13 @@
android:layout_height="wrap_content"
android:id="@+id/taskbar_pinning_switch"
android:background="@null"
- android:clickable="false"
android:gravity="start|center_vertical"
android:textAlignment="viewStart"
android:paddingStart="12dp"
+ android:switchPadding="12dp"
android:layout_weight="1"
android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
android:lines="1"
- android:ellipsize="end"
android:textSize="14sp"
android:textColor="?android:attr/textColorPrimary"
android:text="@string/always_show_taskbar" />
diff --git a/quickstep/res/layout/taskbar_edu_search.xml b/quickstep/res/layout/taskbar_edu_search.xml
new file mode 100644
index 0000000..ca84f35
--- /dev/null
+++ b/quickstep/res/layout/taskbar_edu_search.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.TaskbarEduTooltip.Title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:text="@string/taskbar_search_edu_title"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/search_edu_animation" />
+
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/search_edu_animation"
+ android:layout_width="@dimen/taskbar_edu_swipe_lottie_width"
+ android:layout_height="@dimen/taskbar_edu_swipe_lottie_height"
+ android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
+ app:layout_constraintBottom_toTopOf="@id/search_edu_text"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/title"
+ app:lottie_rawRes="@raw/taskbar_edu_search"
+ app:lottie_autoPlay="true"
+ app:lottie_loop="true" />
+
+ <TextView
+ android:id="@+id/search_edu_text"
+ style="@style/TextAppearance.TaskbarEduTooltip.Subtext"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textSize="@dimen/taskbar_edu_search_subtitle_text_size"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/search_edu_animation"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/quickstep/res/layout/taskbar_nav_button.xml b/quickstep/res/layout/taskbar_nav_button.xml
index aea4885..8f1c904 100644
--- a/quickstep/res/layout/taskbar_nav_button.xml
+++ b/quickstep/res/layout/taskbar_nav_button.xml
@@ -19,6 +19,7 @@
android:layout_width="@dimen/taskbar_nav_buttons_size"
android:layout_height="@dimen/taskbar_nav_buttons_size"
android:background="@drawable/taskbar_icon_click_feedback_roundrect"
+ android:focusable="false"
android:scaleType="center"
android:tint="@color/taskbar_nav_icon_light_color"
tools:ignore="UseAppTint" />
\ No newline at end of file
diff --git a/quickstep/res/layout/transient_taskbar.xml b/quickstep/res/layout/transient_taskbar.xml
index 6af7cf4..f3c3383 100644
--- a/quickstep/res/layout/transient_taskbar.xml
+++ b/quickstep/res/layout/transient_taskbar.xml
@@ -41,11 +41,10 @@
<com.android.launcher3.taskbar.bubbles.BubbleBarView
android:id="@+id/taskbar_bubbles"
android:layout_width="wrap_content"
- android:layout_height="@dimen/bubblebar_size"
+ android:layout_height="@dimen/bubblebar_size_with_pointer"
android:layout_gravity="bottom|end"
- android:layout_marginEnd="@dimen/transient_taskbar_bottom_margin"
- android:paddingEnd="@dimen/taskbar_icon_spacing"
- android:paddingStart="@dimen/taskbar_icon_spacing"
+ android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
+ android:paddingTop="@dimen/bubblebar_pointer_visible_size"
android:visibility="gone"
android:gravity="center"
android:clipChildren="false"
diff --git a/quickstep/res/raw-h480dp/all_set_page_bg.json b/quickstep/res/raw-h480dp/all_set_page_bg.json
new file mode 100644
index 0000000..f2998a0
--- /dev/null
+++ b/quickstep/res/raw-h480dp/all_set_page_bg.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":701,"h":841,"nm":"SUW_WelcomeScreen_FoldableOpen_Portrait_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[55]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.949},"o":{"x":0.167,"y":0.167},"t":0,"s":[181.172,148.425,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.038},"t":95,"s":[181.172,-68.377,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[181.172,148.425,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[21.6,21.6,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.594,-128.921],[118.85,-93.491],[148.984,-67.406],[148.684,-27.55],[163.244,9.552],[144.457,44.702],[140.106,84.321],[107.136,106.715],[84.872,139.773],[45.271,144.279],[10.194,163.205],[-26.964,148.792],[-66.818,149.249],[-93.023,119.218],[-128.524,101.101],[-137.771,62.332],[-160.786,29.793],[-150.957,-8.833],[-156.215,-48.341],[-129.561,-77.974],[-115.856,-115.401],[-78.484,-129.253],[-48.956,-156.023],[-9.427,-150.921],[29.159,-160.903],[61.789,-138.015]],"o":[[-100.594,128.921],[-118.85,93.491],[-148.984,67.406],[-148.684,27.55],[-163.244,-9.552],[-144.456,-44.702],[-140.106,-84.321],[-107.136,-106.715],[-84.872,-139.773],[-45.271,-144.279],[-10.194,-163.205],[26.964,-148.792],[66.818,-149.249],[93.023,-119.218],[128.524,-101.101],[137.771,-62.332],[160.786,-29.793],[150.957,8.833],[156.215,48.341],[129.561,77.974],[115.856,115.4],[78.484,129.253],[48.956,156.023],[9.427,150.921],[-29.159,160.903],[-61.789,138.015]],"v":[[975.226,761.299],[707.165,899.424],[509.8,1127.42],[208.253,1125.148],[-72.46,1235.308],[-338.41,1093.162],[-638.164,1060.25],[-807.592,810.792],[-1057.715,642.348],[-1091.808,342.727],[-1235.002,77.338],[-1125.948,-203.807],[-1129.407,-505.342],[-902.191,-703.604],[-765.123,-972.207],[-471.796,-1042.166],[-225.603,-1216.305],[66.637,-1141.935],[365.557,-1181.715],[589.761,-980.053],[872.928,-876.361],[977.734,-593.606],[1180.277,-370.197],[1141.675,-71.124],[1217.196,220.821],[1044.029,467.699]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.713725490196,0.556862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9.3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".tertiary","cl":"tertiary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.619]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[67]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.263]},"t":95,"s":[82]},{"t":180,"s":[67]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.927]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[458.803]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.05]},"t":95,"s":[536.803]},{"t":180,"s":[458.803]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[707.143]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.069]},"t":95,"s":[639.643]},{"t":180,"s":[707.143]}],"ix":4}},"a":{"k":[{"s":[164.438,1433.781,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[164.438,1433.781,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"a":0,"k":[-30,30,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2361.125,4541.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.901960784314,0.764705882353,0.423529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[350.5,420.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[420.5,-350.5],[-420.5,-350.5],[-420.5,350.5],[420.5,350.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/quickstep/res/raw-w840dp-h480dp/all_set_page_bg.json b/quickstep/res/raw-w840dp-h480dp/all_set_page_bg.json
new file mode 100644
index 0000000..ae1b560
--- /dev/null
+++ b/quickstep/res/raw-w840dp-h480dp/all_set_page_bg.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":841,"h":701,"nm":"SUW_WelcomeScreen_FelixOpen_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[55]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.939},"o":{"x":0.167,"y":0.167},"t":0,"s":[140.975,228.318,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.045},"t":95,"s":[140.975,47.65,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[140.975,228.318,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[18,18,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.594,-128.921],[118.85,-93.491],[148.984,-67.406],[148.684,-27.55],[163.244,9.552],[144.457,44.702],[140.106,84.321],[107.136,106.715],[84.872,139.773],[45.271,144.279],[10.194,163.205],[-26.964,148.792],[-66.818,149.249],[-93.023,119.218],[-128.524,101.101],[-137.771,62.332],[-160.786,29.793],[-150.957,-8.833],[-156.215,-48.341],[-129.561,-77.974],[-115.856,-115.401],[-78.484,-129.253],[-48.956,-156.023],[-9.427,-150.921],[29.159,-160.903],[61.789,-138.015]],"o":[[-100.594,128.921],[-118.85,93.491],[-148.984,67.406],[-148.684,27.55],[-163.244,-9.552],[-144.456,-44.702],[-140.106,-84.321],[-107.136,-106.715],[-84.872,-139.773],[-45.271,-144.279],[-10.194,-163.205],[26.964,-148.792],[66.818,-149.249],[93.023,-119.218],[128.524,-101.101],[137.771,-62.332],[160.786,-29.793],[150.957,8.833],[156.215,48.341],[129.561,77.974],[115.856,115.4],[78.484,129.253],[48.956,156.023],[9.427,150.921],[-29.159,160.903],[-61.789,138.015]],"v":[[975.226,761.299],[707.165,899.424],[509.8,1127.42],[208.253,1125.148],[-72.46,1235.308],[-338.41,1093.162],[-638.164,1060.25],[-807.592,810.792],[-1057.715,642.348],[-1091.808,342.727],[-1235.002,77.338],[-1125.948,-203.807],[-1129.407,-505.342],[-902.191,-703.604],[-765.123,-972.207],[-471.796,-1042.166],[-225.603,-1216.305],[66.637,-1141.935],[365.557,-1181.715],[589.761,-980.053],[872.928,-876.361],[977.734,-593.606],[1180.277,-370.197],[1141.675,-71.124],[1217.196,220.821],[1044.029,467.699]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.713725490196,0.556862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[11.111],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[11.111],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".tertiary","cl":"tertiary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.619]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[67]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.263]},"t":95,"s":[82]},{"t":180,"s":[67]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.913]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[639]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.06]},"t":95,"s":[704]},{"t":180,"s":[639]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.12]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[527.25]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.083]},"t":95,"s":[471]},{"t":180,"s":[527.25]}],"ix":4}},"a":{"k":[{"s":[164.438,1433.781,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[164.438,1433.781,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"a":0,"k":[-25,25,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2361.125,4541.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.901960784314,0.764705882353,0.423529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[6],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[6],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[420.5,350.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[420.5,-350.5],[-420.5,-350.5],[-420.5,350.5],[420.5,350.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/quickstep/res/raw/taskbar_edu_search.json b/quickstep/res/raw/taskbar_edu_search.json
new file mode 100644
index 0000000..0dcccd6
--- /dev/null
+++ b/quickstep/res/raw/taskbar_edu_search.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":431,"w":348,"h":218,"nm":"Omni_EDU_05_matted","ddd":0,"assets":[{"id":"comp_0","nm":"Omni_EDU_05","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-140,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-48,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":174,"s":[0,0]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":192,"s":[17,17]},{"i":{"x":[0.999,0.999],"y":[1,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":237,"s":[17,17]},{"t":249,"s":[0,0]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-48],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":174,"op":249,"st":40,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"c2s null","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":193,"s":[-56.933,-189.286,0],"to":[-0.023,-0.021,0],"ti":[0.092,0.088,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":194,"s":[-57.069,-189.414,0],"to":[-0.092,-0.088,0],"ti":[0.189,0.184,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":195,"s":[-57.488,-189.813,0],"to":[-0.189,-0.184,0],"ti":[0.29,0.293,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":196,"s":[-58.204,-190.519,0],"to":[-0.29,-0.293,0],"ti":[0.392,0.419,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":197,"s":[-59.226,-191.574,0],"to":[-0.392,-0.419,0],"ti":[0.492,0.566,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":198,"s":[-60.555,-193.035,0],"to":[-0.492,-0.566,0],"ti":[0.582,0.741,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":199,"s":[-62.177,-194.971,0],"to":[-0.582,-0.741,0],"ti":[0.641,0.957,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":200,"s":[-64.048,-197.48,0],"to":[-0.641,-0.957,0],"ti":[0.644,1.22,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":201,"s":[-66.021,-200.712,0],"to":[-0.644,-1.22,0],"ti":[0.59,1.52,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":202,"s":[-67.912,-204.803,0],"to":[-0.59,-1.52,0],"ti":[0.478,1.841,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":203,"s":[-69.561,-209.83,0],"to":[-0.478,-1.841,0],"ti":[0.309,2.174,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":204,"s":[-70.781,-215.849,0],"to":[-0.309,-2.174,0],"ti":[0.095,2.509,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":205,"s":[-71.414,-222.876,0],"to":[-0.095,-2.509,0],"ti":[-0.149,2.838,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":206,"s":[-71.348,-230.903,0],"to":[0.149,-2.838,0],"ti":[-0.493,3.137,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":207,"s":[-70.519,-239.902,0],"to":[0.493,-3.137,0],"ti":[-0.971,3.37,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":208,"s":[-68.392,-249.724,0],"to":[0.971,-3.37,0],"ti":[-1.521,3.501,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":209,"s":[-64.693,-260.12,0],"to":[1.521,-3.501,0],"ti":[-2.183,3.438,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":210,"s":[-59.267,-270.731,0],"to":[2.183,-3.438,0],"ti":[-3.014,2.996,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":211,"s":[-51.596,-280.747,0],"to":[3.014,-2.996,0],"ti":[-3.718,2.327,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":212,"s":[-41.184,-288.709,0],"to":[3.718,-2.327,0],"ti":[-4.093,1.634,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":213,"s":[-29.289,-294.708,0],"to":[4.093,-1.634,0],"ti":[-4.243,0.814,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":214,"s":[-16.628,-298.511,0],"to":[4.243,-0.814,0],"ti":[-4.168,-0.028,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":215,"s":[-3.828,-299.591,0],"to":[4.168,0.028,0],"ti":[-3.907,-0.628,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":216,"s":[8.377,-298.344,0],"to":[3.907,0.628,0],"ti":[-3.551,-1.009,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":217,"s":[19.614,-295.822,0],"to":[3.551,1.009,0],"ti":[-3.138,-1.314,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":218,"s":[29.681,-292.288,0],"to":[3.138,1.314,0],"ti":[-2.68,-1.566,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":219,"s":[38.443,-287.941,0],"to":[2.68,1.566,0],"ti":[-2.137,-1.816,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":220,"s":[45.761,-282.889,0],"to":[2.137,1.816,0],"ti":[-1.616,-1.954,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":221,"s":[51.267,-277.046,0],"to":[1.616,1.954,0],"ti":[-1.251,-1.903,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":222,"s":[55.456,-271.165,0],"to":[1.251,1.903,0],"ti":[-0.99,-1.774,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":223,"s":[58.77,-265.626,0],"to":[0.99,1.774,0],"ti":[-0.752,-1.637,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":224,"s":[61.394,-260.52,0],"to":[0.752,1.637,0],"ti":[-0.54,-1.496,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":225,"s":[63.282,-255.805,0],"to":[0.54,1.496,0],"ti":[-0.388,-1.34,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":226,"s":[64.634,-251.545,0],"to":[0.388,1.34,0],"ti":[-0.278,-1.183,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":227,"s":[65.61,-247.762,0],"to":[0.278,1.183,0],"ti":[-0.195,-1.031,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":228,"s":[66.303,-244.447,0],"to":[0.195,1.031,0],"ti":[-0.13,-0.885,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":229,"s":[66.778,-241.579,0],"to":[0.13,0.885,0],"ti":[-0.081,-0.748,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":230,"s":[67.085,-239.135,0],"to":[0.081,0.748,0],"ti":[-0.045,-0.619,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":231,"s":[67.265,-237.089,0],"to":[0.045,0.619,0],"ti":[-0.02,-0.498,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":232,"s":[67.354,-235.418,0],"to":[0.02,0.498,0],"ti":[-0.004,-0.385,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":233,"s":[67.382,-234.099,0],"to":[0.004,0.385,0],"ti":[0.004,-0.279,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":234,"s":[67.377,-233.109,0],"to":[-0.004,0.279,0],"ti":[0.006,-0.18,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":235,"s":[67.358,-232.425,0],"to":[-0.006,0.18,0],"ti":[0.004,-0.087,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":236,"s":[67.341,-232.028,0],"to":[-0.004,0.087,0],"ti":[0.001,-0.021,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":237,"s":[67.335,-231.9,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":248,"s":[67.335,-231.9,0],"to":[-0.008,0.042,0],"ti":[0.033,-0.177,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":249,"s":[67.289,-231.647,0],"to":[-0.033,0.177,0],"ti":[0.079,-0.372,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":250,"s":[67.135,-230.84,0],"to":[-0.079,0.372,0],"ti":[0.155,-0.591,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":251,"s":[66.815,-229.413,0],"to":[-0.155,0.591,0],"ti":[0.268,-0.834,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":252,"s":[66.203,-227.297,0],"to":[-0.268,0.834,0],"ti":[0.413,-1.109,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":253,"s":[65.206,-224.409,0],"to":[-0.413,1.109,0],"ti":[0.247,-0.628,0]},{"t":254,"s":[63.723,-220.641,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":193,"op":264,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":0,"k":90,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.149,"y":0.701},"o":{"x":0.4,"y":0.002},"t":248,"s":[-8.212,-253.668,0],"to":[0,0,0],"ti":[0,0,0]},{"t":263,"s":[0,-227,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.149,"y":0.701},"o":{"x":0.4,"y":0.002},"t":248,"s":[{"i":[[3.564,-2.549],[3.809,-1.264],[12.238,3.984],[8.325,10.606],[-0.214,12.436],[-2.655,8.03],[-10.533,8.871],[-9.434,2.582],[-8.48,-3.643],[-9.227,-13.6],[-1.802,-9.359],[2.171,-9.596]],"o":[[-3.564,2.549],[-12.609,4.183],[-12.52,-4.076],[-7.722,-9.838],[0.146,-8.454],[4.364,-13.197],[5.142,-4.331],[9.891,-2.707],[11.828,5.081],[4.099,6.042],[2.254,11.707],[-3.907,17.266]],"v":[[53.514,60.456],[41.519,66.798],[2.462,64.221],[-33.286,39.729],[-45.626,-0.393],[-41.723,-21.691],[-18.543,-58.238],[5.442,-69.196],[39.994,-68.523],[68.082,-43.976],[77.052,-19.838],[77.406,14.145]],"c":true}]},{"t":263,"s":[{"i":[[7.701,-4.092],[8.127,0],[7.226,4.357],[4.092,7.701],[0,8.127],[-4.357,7.226],[-7.701,4.092],[-8.127,0],[-7.226,-4.357],[-4.092,-7.701],[0,-8.127],[4.357,-7.226]],"o":[[-6.709,3.565],[-9.053,0],[-7.447,-4.489],[-3.565,-6.709],[0,-9.053],[4.489,-7.447],[6.709,-3.565],[9.053,0],[7.447,4.489],[3.565,6.709],[0,9.053],[-4.489,7.447]],"v":[[22.492,42.415],[0,48],[-24.747,41.137],[-42.415,22.492],[-48,0],[-41.137,-24.747],[-22.492,-42.415],[0,-48],[24.747,-41.137],[42.415,-22.492],[48,0],[41.137,24.747]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":78.63,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 303","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":193,"s":[0]},{"t":237,"s":[63],"h":1},{"i":{"x":[0.149],"y":[0.701]},"o":{"x":[0.4],"y":[0.002]},"t":248,"s":[63]},{"t":263,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim Paths 2","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":193,"op":263,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[0]},{"t":289,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[2.625,-203,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[2.625,-221,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.5,1.3],[0,0],[0,0],[0,0],[-0.65,1.45],[-0.25,1.6],[0,0],[2.65,-2.25],[3.6,0],[2.9,2.85],[0,4.05],[-2.85,2.85],[-4.1,0],[-1.1,-0.3],[-1,-0.5],[0,0],[1.7,0.5],[1.8,0],[3.75,-3.75],[0,-5.3],[-3.75,-3.75],[-5.4,0],[-2,0.7]],"o":[[0,0],[0,0],[0,0],[1.05,-1.35],[0.7,-1.5],[0,0],[-0.7,3.35],[-2.6,2.2],[-4.1,0],[-2.85,-2.9],[0,-4.05],[2.9,-2.9],[1.2,0],[1.1,0.25],[0,0],[-1.45,-0.85],[-1.65,-0.5],[-5.35,0],[-3.75,3.75],[0,5.3],[3.75,3.75],[2.25,0],[2,-0.75]],"v":[[1.35,9.863],[21.075,29.587],[24.375,26.288],[4.575,6.637],[7.125,2.438],[8.55,-2.213],[3.975,-2.213],[-1.05,6.188],[-10.35,9.488],[-20.85,5.212],[-25.125,-5.213],[-20.85,-15.562],[-10.35,-19.913],[-6.9,-19.462],[-3.75,-18.337],[-0.45,-21.637],[-5.175,-23.663],[-10.35,-24.413],[-24,-18.788],[-29.625,-5.213],[-24,8.363],[-10.275,13.988],[-3.9,12.938]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-3.05,-3.05],[0,-4.4],[-3.05,3.05],[-4.4,0],[3.05,3.05],[0,4.4],[3.05,-3.05],[4.4,0]],"o":[[3.05,3.05],[0,-4.4],[3.05,-3.05],[-4.4,0],[-3.05,-3.05],[0,4.4],[-3.05,3.05],[4.4,0]],"v":[[9.3,-9.262],[13.875,1.913],[18.45,-9.262],[29.625,-13.837],[18.45,-18.413],[13.875,-29.587],[9.3,-18.413],[-1.875,-13.837]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":60,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[-100,-161,0]}],"ix":2,"l":2},"a":{"a":0,"k":[11,11,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[{"i":[[0,0],[7.26,-4.415],[4.064,-7.787],[0,-8]],"o":[[-9.118,0],[-7.478,4.547],[-3.462,6.633],[0,0]],"v":[[11,-37],[-13.903,-30.043],[-31.581,-11.176],[-37,11]],"c":false}]},{"t":290,"s":[{"i":[[0,0],[0,0],[0,-7.732],[0,0]],"o":[[0,0],[-7.732,0],[0,0],[0,0]],"v":[[11,-11],[3,-11],[-11,3],[-11,11]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Handle Top Left","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":63,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[100,-161,0]}],"ix":2,"l":2},"a":{"a":0,"k":[11,11,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[{"i":[[0,0],[7.26,-4.415],[4.064,-7.787],[0,-8]],"o":[[-9.118,0],[-7.478,4.547],[-3.462,6.633],[0,0]],"v":[[11,-37],[-13.903,-30.043],[-31.581,-11.176],[-37,11]],"c":false}]},{"t":290,"s":[{"i":[[0,0],[0,0],[0,-7.732],[0,0]],"o":[[0,0],[-7.732,0],[0,0],[0,0]],"v":[[11,-11],[3,-11],[-11,3],[-11,11]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Handle Top Left","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":63,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[100,-279,0]}],"ix":2,"l":2},"a":{"a":0,"k":[11,11,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[{"i":[[0,0],[7.26,-4.415],[4.064,-7.787],[0,-8]],"o":[[-9.118,0],[-7.478,4.547],[-3.462,6.633],[0,0]],"v":[[11,-37],[-13.903,-30.043],[-31.581,-11.176],[-37,11]],"c":false}]},{"t":290,"s":[{"i":[[0,0],[0,0],[0,-7.732],[0,0]],"o":[[0,0],[-7.732,0],[0,0],[0,0]],"v":[[11,-11],[3,-11],[-11,3],[-11,11]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Handle Top Left","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":63,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[-100,-279,0]}],"ix":2,"l":2},"a":{"a":0,"k":[11,11,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[{"i":[[0,0],[7.26,-4.415],[4.064,-7.787],[0,-8]],"o":[[-9.118,0],[-7.478,4.547],[-3.462,6.633],[0,0]],"v":[[11,-37],[-13.903,-30.043],[-31.581,-11.176],[-37,11]],"c":false}]},{"t":290,"s":[{"i":[[0,0],[0,0],[0,-7.732],[0,0]],"o":[[0,0],[-7.732,0],[0,0],[0,0]],"v":[[11,-11],[3,-11],[-11,3],[-11,11]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Handle Top Left","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":63,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"Search","parent":12,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-166,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[0,-109,0]}],"ix":2,"l":2},"a":{"a":0,"k":[83,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":166,"h":45,"ip":135,"op":319,"st":135,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Flower","parent":12,"refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.5,-209,0],"ix":2,"l":2},"a":{"a":0,"k":[109,115,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":219,"h":231,"ip":122,"op":431,"st":122,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue50","cl":"blue50","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":266,"s":[0]},{"t":278,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[0,-220,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":263,"s":[92,92]},{"t":299,"s":[240,158]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":263,"s":[46]},{"t":299,"s":[12]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":60,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":3,"nm":"pan up","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.15,"y":1},"o":{"x":0.653,"y":0},"t":99,"s":[174,109,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.15,"y":0.15},"o":{"x":0.546,"y":0.546},"t":169,"s":[174,327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.15,"y":1},"o":{"x":0.653,"y":0},"t":357,"s":[174,327,0],"to":[0,0,0],"ti":[0,0,0]},{"t":427,"s":[174,109,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100,"ix":1}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100,"ix":2}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0,"ix":4}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0,"ix":5}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"long Press Over - Outward","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":33,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":95,"s":[100]},{"t":113,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,38,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":83,"s":[116,116]},{"t":113,"s":[136,136]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Long Pres Over","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":83,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"long Press Over","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":33,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":83,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":86,"s":[100]},{"t":95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,38,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.999,0.999],"y":[1,1]},"o":{"x":[0.001,0.001],"y":[0,0]},"t":33,"s":[40,40]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":83,"s":[120,120]},{"t":101,"s":[40,40]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Long Pres Over","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":0,"nm":"Action Key","parent":12,"refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.539,38,0],"ix":2,"l":2},"a":{"a":0,"k":[53.5,53.5,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":107,"h":107,"ip":0,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"long Press Under","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":42,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":83,"s":[100]},{"t":95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,38,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":30,"s":[96,96]},{"i":{"x":[0.833,0.833],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":66,"s":[120,120]},{"i":{"x":[0.999,0.999],"y":[1,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":83,"s":[120,120]},{"t":101,"s":[106,106]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529422283,0.890196084976,0.988235294819,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Long Press Under","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".grey800","cl":"grey800","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,38,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[348,142],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294118524,0.250980407,0.262745112181,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Grey800","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue100","cl":"blue100","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[174,109,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[348,218],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.890196078431,0.988235294118,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":431,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"SearchPill","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"superG","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[0]},{"t":17,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[-3.898,0.128,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.239,0.128,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.311,0.128,0],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.286,0.128,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.519,0.128,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-15.81,0.128,0],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.898,0.128,0],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-37.431,0.128,0],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-43.055,0.128,0],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-46.404,0.128,0],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-48.712,0.128,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-50.434,0.128,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-51.781,0.128,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.869,0.128,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.768,0.128,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.522,0.128,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.162,0.128,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.71,0.128,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.182,0.128,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.591,0.128,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.947,0.128,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.257,0.128,0],"t":21,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.526,0.128,0],"t":22,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.761,0.128,0],"t":23,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.965,0.128,0],"t":24,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.142,0.128,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.295,0.128,0],"t":26,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.425,0.128,0],"t":27,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.537,0.128,0],"t":28,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.63,0.128,0],"t":29,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.707,0.128,0],"t":30,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.769,0.128,0],"t":31,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.818,0.128,0],"t":32,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.854,0.128,0],"t":33,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.853,0.128,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.679,0.128,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.301,0.128,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.701,0.128,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.039,0.128,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.46,0.128,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.983,0.128,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.588,0.128,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.255,0.128,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.97,0.128,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.723,0.128,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.507,0.128,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.316,0.128,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.147,0.128,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.996,0.128,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.861,0.128,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.74,0.128,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.631,0.128,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.534,0.128,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.446,0.128,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.367,0.128,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.297,0.128,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.234,0.128,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.178,0.128,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.128,0.128,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.084,0.128,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.046,0.128,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.012,0.128,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.96,0.128,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.913,0.128,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[24.102,22.628,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.788,0],[0.959,-2.878],[0,0],[-4.737,0],[-2.091,-1.959]],"o":[[-1.262,-1.202],[-3.161,0],[0,0],[1.99,-3.959],[3.272,0],[0,0]],"v":[[6.004,1.712],[1.358,-0.106],[-5.439,4.903],[-9.458,1.783],[1.358,-4.903],[9.458,-1.742]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917647063732,0.262745112181,0.207843139768,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[23.001,15.411],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"g-red","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-0.808],[-0.252,-0.727],[0,0],[0,1.959],[-0.828,1.636],[0,0],[0,0]],"o":[[0,0.808],[0,0],[-0.828,-1.636],[0,-1.959],[0,0],[0,0],[-0.242,0.727]],"v":[[2.277,0],[2.661,2.313],[-1.358,5.434],[-2.661,0],[-1.358,-5.434],[2.661,-5.434],[2.661,-2.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.670588254929,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[14.901,22.628],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"g-yellow","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[3.272,0],[1.99,3.959],[0,0],[0,0],[-3.161,0],[-1.091,0.727]],"o":[[-2,1.848],[-4.737,0],[0,0],[0,0],[0.959,2.878],[1.636,0],[0,0]],"v":[[9.413,1.964],[1.404,4.903],[-9.413,-1.783],[-9.413,-4.903],[-5.393,-4.903],[1.404,0.106],[5.514,-1.066]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.203921571374,0.658823549747,0.32549020648,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[22.955,29.844],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"g-green","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-0.798],[2.283,-2.111],[0,0],[0,0],[-0.293,1.495],[0,0],[0,0]],"o":[[0.121,0.737],[0,3.686],[0,0],[0,0],[1.273,-0.858],[0,0],[0,0],[0,0]],"v":[[5.61,-5.6],[5.802,-3.308],[2.207,5.6],[-1.692,5.6],[-1.692,2.57],[0.732,-1.045],[-5.802,-1.045],[-5.802,-5.6]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258823543787,0.521568655968,0.956862747669,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[30.161,26.208],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"g-blue","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"mic","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[0]},{"t":17,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[2.7,0.705,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.041,0.705,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.113,0.705,0],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[6.088,0.705,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[9.321,0.705,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[14.611,0.705,0],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[24.7,0.705,0],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.233,0.705,0],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.857,0.705,0],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.206,0.705,0],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.514,0.705,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.235,0.705,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.583,0.705,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.671,0.705,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.569,0.705,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.323,0.705,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.963,0.705,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.511,0.705,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.984,0.705,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.393,0.705,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.749,0.705,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.058,0.705,0],"t":21,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.328,0.705,0],"t":22,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.563,0.705,0],"t":23,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.767,0.705,0],"t":24,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.944,0.705,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.096,0.705,0],"t":26,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.227,0.705,0],"t":27,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.338,0.705,0],"t":28,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.431,0.705,0],"t":29,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.509,0.705,0],"t":30,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.571,0.705,0],"t":31,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.62,0.705,0],"t":32,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.656,0.705,0],"t":33,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.655,0.705,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.48,0.705,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.103,0.705,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.503,0.705,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.841,0.705,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.262,0.705,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.785,0.705,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.39,0.705,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.057,0.705,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.772,0.705,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.525,0.705,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.309,0.705,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.118,0.705,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.949,0.705,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.798,0.705,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.663,0.705,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.542,0.705,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.433,0.705,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.335,0.705,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.248,0.705,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.169,0.705,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.098,0.705,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.035,0.705,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.979,0.705,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.929,0.705,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.886,0.705,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.847,0.705,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.814,0.705,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.786,0.705,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.742,0.705,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[140.7,23.205,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.688,0],[0,0],[0.012,3.389],[0,0],[4.735,0],[1.554,1.554]],"o":[[1.113,1.113],[0,0],[3.377,0],[0,0],[0,4.723],[-2.361,0],[0,0]],"v":[[-5.573,0.049],[-1.242,1.848],[-1.242,1.848],[4.851,-4.282],[7.298,-4.282],[-1.242,4.282],[-7.298,1.774]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917647063732,0.262745112181,0.207843139768,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[141.954,27.475],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"mic-red","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,2.361],[0,0],[0,0],[-1.101,-1.113],[0,0]],"o":[[0,0],[0,0],[0,1.688],[0,0],[-1.554,-1.554]],"v":[[-2.117,-3.028],[0.33,-3.028],[0.33,-3.028],[2.117,1.303],[0.392,3.028]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.670588254929,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[134.264,26.233],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"mic-yellow","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[1.224,-1.884],[-1.224,-1.884],[-1.224,1.884],[1.224,1.884]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.203921571374,0.658823549747,0.32549020648,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[140.712,33.556],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"mic-green","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[2.031,0],[0,0],[0,2.031],[0,0],[-2.031,0],[0,-2.031]],"o":[[0,2.031],[0,0],[-2.031,0],[0,0],[0,-2.031],[2.031,0],[0,0]],"v":[[3.658,4.282],[0,7.953],[0,7.953],[-3.671,4.282],[-3.671,-4.282],[0,-7.953],[3.671,-4.282]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258823543787,0.521568655968,0.956862747669,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[140.712,18.923],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"mic-blue","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey800","cl":"grey800","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"t":12,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[83,22.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[0.15,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":0,"s":[56,45]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0]},"t":6,"s":[100,45]},{"i":{"x":[0.833,0.833],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":36,"s":[166,45]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":128,"s":[166,45]},{"t":164,"s":[154,45]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":87,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294118524,0.250980407,0.262745112181,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Grey 800","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Flower","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[109.453,193.777,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[36.517,1.481],[-36.517,1.481]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Union 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[5.448,5.448],"ix":2},"p":{"a":0,"k":[-12.903,0.471],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[5.448,5.448],"ix":2},"p":{"a":0,"k":[9.094,0.471],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[5.448,5.448],"ix":2},"p":{"a":0,"k":[2.554,0.471],"ix":3},"nm":"Ellipse Path 3","mn":"ADBE Vector Shape - Ellipse","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[6.588,6.588],"ix":2},"p":{"a":0,"k":[-8.927,-0.099],"ix":3},"nm":"Ellipse Path 4","mn":"ADBE Vector Shape - Ellipse","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[6.588,6.588],"ix":2},"p":{"a":0,"k":[5.118,-0.099],"ix":3},"nm":"Ellipse Path 5","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Union","np":7,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64.952,63.598,0],"ix":2,"l":2},"a":{"a":0,"k":[24.952,23.598,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.753,0],[0,1.753],[1.753,0],[0,-1.753]],"o":[[1.753,0],[0,-1.753],[-1.753,0],[0,1.753]],"v":[[0,3.174],[3.174,0],[0,-3.174],[-3.174,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[21.137,19.31],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"flower 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.753,0],[0,1.753],[1.753,0],[0,-1.753]],"o":[[1.753,0],[0,-1.753],[-1.753,0],[0,1.753]],"v":[[0,3.174],[3.174,0],[0,-3.174],[-3.174,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[28.767,23.097],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"flower 2","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.753,0],[0,1.753],[1.753,0],[0,-1.753]],"o":[[1.753,0],[0,-1.753],[-1.753,0],[0,1.753]],"v":[[0,3.174],[3.174,0],[0,-3.174],[-3.174,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[21.75,27.886],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"flower 3","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".yellow400","cl":"yellow400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[63.114,63.125,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.392,1.281],[2.395,-2.283],[1.893,0.223],[0.557,-3.174],[1.726,-0.891],[-1.504,-2.896],[0.835,-1.671],[-2.951,-1.504],[-0.278,-1.782],[-3.341,0.446],[-1.392,-1.281],[-2.395,2.283],[-1.893,-0.278],[-0.557,3.174],[-1.726,0.891],[1.504,2.896],[-0.835,1.671],[2.951,1.504],[0.334,1.838],[3.341,-0.446]],"o":[[-2.45,-2.283],[-1.392,1.337],[-3.341,-0.501],[-0.334,1.838],[-2.951,1.504],[0.835,1.671],[-1.504,2.896],[1.671,0.835],[0.557,3.174],[1.949,-0.278],[2.45,2.283],[1.392,-1.281],[3.341,0.501],[0.334,-1.838],[2.951,-1.504],[-0.835,-1.671],[1.504,-2.896],[-1.726,-0.891],[-0.557,-3.174],[-1.949,0.278]],"v":[[4.316,-21.412],[-4.372,-21.412],[-9.551,-19.741],[-16.567,-14.841],[-19.797,-10.553],[-22.47,-2.59],[-22.47,2.701],[-19.797,10.664],[-16.623,14.897],[-9.606,19.797],[-4.372,21.412],[4.316,21.412],[9.551,19.797],[16.567,14.897],[19.797,10.609],[22.47,2.645],[22.47,-2.645],[19.797,-10.609],[16.567,-14.897],[9.551,-19.797]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.988235294819,0.78823530674,0.203921571374,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[97.335,135.554,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[10.748,5.402]],"o":[[0,0],[-0.891,-40.374],[0,0]],"v":[[16.706,59.315],[16.706,7.288],[-16.706,-59.315]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":3,"nm":"▽ Right Flower","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[144.155,88.602,0],"ix":2,"l":2},"a":{"a":0,"k":[31.228,40.542,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".black","cl":"black","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[45.33,17.598,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.184,0],[0,2.184],[2.184,0],[0,-2.184]],"o":[[2.184,0],[0,-2.184],[-2.184,0],[0,2.184]],"v":[[0,3.954],[3.954,0],[0,-3.954],[-3.954,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".red400","cl":"red400","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[45.024,17.515,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.167,2.283],[4.678,-6.014],[2.283,-0.223],[-5.959,-4.733],[-0.167,-2.283],[-4.678,6.014],[-2.283,0.223],[5.959,4.733]],"o":[[-0.613,-7.574],[-1.448,1.782],[-7.629,0.668],[1.838,1.448],[0.613,7.574],[1.392,-1.838],[7.629,-0.668],[-1.838,-1.448]],"v":[[11.054,-9.829],[-3.87,-14.284],[-9.718,-11.11],[-14.228,3.926],[-11.054,9.829],[3.87,14.284],[9.718,11.11],[14.228,-3.926]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333337307,0.403921574354,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".green400","cl":"green400","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[22.485,51.377,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[9.244,-14.2],[-4.678,11.138],[7.407,2.729]],"o":[[7.741,4.121],[1.002,-2.951],[-5.457,-1.448]],"v":[[-14.689,4.256],[14.269,2.642],[7.586,-8.663]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".green400","cl":"green400","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.651,52.32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-10.414,5.68]],"o":[[1.726,-32.355],[0,0]],"v":[[-16.651,28.763],[16.651,-28.763]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[85.004,158.386,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-17.306]],"o":[[34.304,21.05],[0,0]],"v":[[-23.31,-36.483],[23.31,36.483]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[55.116,132.289,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.847,-0.501],[1.281,-3.62],[-0.78,-2.172],[-3.341,-0.891],[-3.898,2.951],[-2.339,4.01],[1.114,0.501]],"o":[[-4.177,0.39],[-0.668,1.893],[1.169,3.341],[4.733,0.668],[3.731,-2.84],[-1.114,-0.501],[-5.29,-2.228]],"v":[[-5.506,-9.773],[-14.416,-3.09],[-14.361,3.147],[-7.065,9.607],[5.91,6.655],[14.931,-4.315],[11.59,-5.819]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.652,111.9,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.179,3.007],[0.724,0.167],[2.228,-1.392],[-0.446,-4.622],[-4.288,-3.341],[0,-0.056],[-0.724,4.901]],"o":[[-0.668,-0.39],[-2.562,-0.613],[-4.065,2.617],[0.557,5.513],[0.501,0.39],[2.172,-4.511],[0.78,-5.235]],"v":[[4.896,-14.778],[2.836,-15.613],[-4.849,-14.221],[-9.917,-1.97],[-1.731,11.284],[5.23,15.851],[9.685,1.483]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[133.059,138.554,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[3.731,-15.203]],"o":[[-27.844,11.695],[0,0]],"v":[[19.018,-18.878],[-19.018,18.878]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[164.078,125.618,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-6.404,-1.726],[-2.673,2.45],[-0.334,2.005],[0.446,1.392],[4.455,0.613],[1.671,-0.835],[0.223,-0.446],[-0.39,-0.501]],"o":[[3.397,0.891],[1.281,-1.169],[0.278,-1.448],[-1.392,-4.511],[-3.564,-0.501],[-0.446,0.223],[-0.223,0.501],[3.787,5.235]],"v":[[1.642,9.094],[11.777,7.256],[14.339,2.467],[14.061,-1.876],[4.705,-9.506],[-12.057,-5.942],[-14.452,-4.661],[-13.505,-2.879]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"Action Key","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"action icon","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[67.56,56.18,0],"ix":2,"l":2},"a":{"a":0,"k":[67.56,56.18,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.379],[3.379,0],[0,-3.379],[-3.379,0]],"o":[[0,-3.379],[-3.379,0],[0,3.379],[3.379,0]],"v":[[6.144,0],[0,-6.144],[-6.144,0],[0,6.144]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,6.789],[-6.789,0],[0,-6.789],[6.789,0]],"o":[[0,-6.789],[6.789,0],[0,6.789],[-6.789,0]],"v":[[-12.288,0],[0,-12.288],[12.288,0],[0,12.288]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.185,-3.229],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-1.101,1.239],[-1.26,-1.207],[0,0],[1.215,-1.215],[1.205,1.15],[0,0]],"o":[[1.159,-1.304],[0,0],[1.241,1.189],[-1.177,1.177],[0,0],[-1.199,-1.144]],"v":[[1.548,2.22],[5.978,2.043],[14.529,10.239],[14.575,14.619],[10.291,14.669],[1.723,6.491]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100.469,100.469],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Union","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[84.621,73.217],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"mag","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24.577,24.577],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.029,35.842],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"top R","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24.577,24.577],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 3","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[47.241,69.628],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"bottom L","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24.577,24.577],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[47.241,35.841],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"top L","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.603921592236,0.627451002598,0.65098041296,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Action (not dynamic)","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[53.25,53.25,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[106.5,106.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":32,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Action (not dynamic)","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[174,109,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[348,218],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":16,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Omni_EDU_05","tt":1,"tp":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[174,109,0],"ix":2,"l":2},"a":{"a":0,"k":[174,109,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":348,"h":218,"ip":0,"op":431,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index ba78a28..73c8129 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Speld vas"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vormvry"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Rekenaar"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Geen onlangse items nie"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programgebruikinstellings"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Vee alles uit"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Deel"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skermskoot"</string>
<string name="action_split" msgid="2098009717623550676">"Verdeel"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Stoor app-paar"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tik op ’n ander app om verdeelde skerm te gebruik"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Kies ’n ander app as jy verdeelde skerm wil gebruik"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Kanselleer"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Doen meer met die Taakbalk"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Wys altyd die Taakbalk"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Raak en hou die verdeler in om altyd die Taakbalk onderaan jou skerm te wys"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Raak en hou die handelingsleutel om te soek wat op jou skerm is"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Hierdie produk gebruik die gekose deel van jou skerm om te soek. Google se <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>privaatheidsbeleid<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> en <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>diensbepalings<xliff:g id="END_TOS_LINK"></a></xliff:g> geld."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Maak toe"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Klaar"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Tuis"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Skuif na links bo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Skuif na regs onder"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Wys nog # app.}other{Wys nog # apps.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Wys # rekenaarapp.}other{Wys # rekenaarapps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> en <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Voeg nou app by werkskerm"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Kanselleer"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Borrel"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Oorvloei"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> vanaf <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> en nog <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index a5f5359..f9eed39 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"ሰካ"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ነፃ ቅጽ"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ዴስክቶፕ"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"አጋራ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ቅጽበታዊ ገፅ ዕይታ"</string>
<string name="action_split" msgid="2098009717623550676">"ክፈል"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"የመተግበሪያ ጥምረትን አስቀምጥ"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"የተከፈለ ማያ ገጽን ለመጠቀም ሌላ መተግበሪያ መታ ያድርጉ"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"የተከፈለ ማያ ገጽን ለመጠቀም ሌላ መተግበሪያ ይምረጡ"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"ይቅር"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"በተግባር አሞሌው ተጨማሪ ነገር ያድርጉ"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"የተግባር አሞሌውን ሁልጊዜ አሳይ"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"በማያ ገጽዎ ግርጌ ላይ ያለውን የተግባር አሞሌ ሁልጊዜ ለማሳየት፣ መክፈያን ይንኩ እና ይያዙ"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"በማያ ገጽዎ ላይ ያለውን ነገር ለመፈለግ የተግባር ቁልፉን ነክተው ይያዙ"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"ይህ ምርት የተመረጠውን የማያ ገጽዎን ክፍል ለመፈለግ ይጠቀማል። የGoogle <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>የግላዊነት መመሪያ<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> እና <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>የአገልግሎት ውል<xliff:g id="END_TOS_LINK"></a></xliff:g> ተፈጻሚ ናቸው።"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"ዝጋ"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"ተጠናቅቋል"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"መነሻ"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ወደ ላይ/ግራ ይውሰዱ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ወደ ታች/ቀኝ ይውሰዱ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ተጨማሪ # መተግበሪያ አሳይ።}one{ተጨማሪ # መተግበሪያ አሳይ።}other{ተጨማሪ # መተግበሪያዎች አሳይ።}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# የዴስክቶፕ መተግበሪያ አሳይ።}one{# የዴስክቶፕ መተግበሪያ አሳይ።}other{# የዴስክቶፕ መተግበሪያዎች አሳይ።}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> እና <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"መተግበሪያን ወደ ዴስክቶፕ በማከል ላይ"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ይቅር"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"አረፋ"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ትርፍ ፍሰት"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ከ<xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> እና <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ተጨማሪ"</string>
</resources>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index d25b211..7a0be9b 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"تثبيت"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"شكل مجاني"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"الكمبيوتر المكتبي"</string>
<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>
@@ -87,14 +88,15 @@
<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="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>
<string name="action_screenshot" msgid="8171125848358142917">"لقطة شاشة"</string>
<string name="action_split" msgid="2098009717623550676">"تقسيم"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"حفظ استخدام التطبيقين معًا"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"انقر على تطبيق آخر لاستخدام وضع تقسيم الشاشة."</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"اختَر تطبيقًا آخر لاستخدام \"وضع تقسيم الشاشة\""</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"إلغاء"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"إنجاز المزيد باستخدام شريط التطبيقات"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"عرض \"شريط التطبيقات\" دائمًا"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"انقر مع الاستمرار على أداة تقسيم الشاشة لعرض \"شريط التطبيقات\" دائمًا في أسفل الشاشة."</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"انقر مع الاستمرار على مفتاح الإجراء للبحث عن المحتوى الذي يظهر على شاشتك"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"يستخدم هذا المنتج الجزء المحدّد من الشاشة للبحث. تسري <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>سياسة الخصوصية<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> و<xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>بنود الخدمة<xliff:g id="END_TOS_LINK"></a></xliff:g> في Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"إغلاق"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"تم"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"الرئيسية"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"الانتقال إلى يمين الشاشة أو أعلاها"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"الانتقال إلى يسار الشاشة أو أسفلها"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{إظهار تطبيق واحد آخر}zero{إظهار # تطبيق آخر}two{إظهار تطبيقَين آخرَين}few{إظهار # تطبيقات أخرى}many{إظهار # تطبيقًا آخر}other{إظهار # تطبيق آخر}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{عرض تطبيق واحد متوافق مع الكمبيوتر المكتبي}zero{عرض # تطبيق متوافق مع الكمبيوتر المكتبي}two{عرض تطبيقَين متوافقين مع الكمبيوتر المكتبي}few{عرض # تطبيقات متوافقة مع الكمبيوتر المكتبي}many{عرض # تطبيقًا متوافقًا مع الكمبيوتر المكتبي}other{عرض # تطبيق متوافق مع الكمبيوتر المكتبي}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"\"<xliff:g id="APP_NAME_1">%1$s</xliff:g>\" و\"<xliff:g id="APP_NAME_2">%2$s</xliff:g>\""</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"إضافة تطبيق إلى سطح المكتب"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"إلغاء"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"فقاعة"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"القائمة الكاملة"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"\"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\" من \"<xliff:g id="APP_NAME">%2$s</xliff:g>\""</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"\"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>\" و<xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> غيرها"</string>
</resources>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index 4a05bff..d440400 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"পিন"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"শ্বেয়াৰ কৰক"</string>
<string name="action_screenshot" msgid="8171125848358142917">"স্ক্ৰীনশ্বট"</string>
<string name="action_split" msgid="2098009717623550676">"বিভাজন কৰক"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"এপৰ পেয়াৰ ছেভ কৰক"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰিবলৈ অন্য এটা এপত টিপক"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰিবলৈ অন্য এটা এপ্ বাছনি কৰক"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"বাতিল কৰক"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"টাস্কবাৰৰ জৰিয়তে অধিক কাৰ্য সম্পাদন কৰক"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"টাস্কবাৰডাল সদায় দেখুৱাওক"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"আপোনাৰ স্ক্ৰীনৰ তলত সদায় টাস্কবাৰডাল দেখুৱাবলৈ বিভাজকডাল স্পৰ্শ কৰি ধৰি ৰাখক"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"আপোনাৰ স্ক্ৰীনখনত থকা সমল সন্ধান কৰিবলৈ কাৰ্য কীটো স্পৰ্শ কৰি ধৰি ৰাখক"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"এই প্ৰ’ডাক্টটোৱে সন্ধান কৰিবলৈ আপোনাৰ স্ক্ৰীনখনৰ বাছনি কৰা অংশ ব্যৱহাৰ কৰে। Googleৰ <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>গোপনীয়তাৰ নীতি<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> আৰু <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>সেৱাৰ চৰ্তাৱলী<xliff:g id="END_TOS_LINK"></a></xliff:g> প্ৰযোজ্য।"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"বন্ধ কৰক"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"হ’ল"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"গৃহপৃষ্ঠা"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ওপৰৰ বাঁওফাললৈ নিয়ক"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"তলৰ সোঁফাললৈ নিয়ক"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{আৰু # টা এপ্ দেখুৱাওক।}one{আৰু # টা এপ্ দেখুৱাওক।}other{আৰু # টা এপ্ দেখুৱাওক।}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# টা ডেস্কটপ এপ্ দেখুৱাওক।}one{# টা ডেস্কটপ এপ্ দেখুৱাওক।}other{# টা ডেস্কটপ এপ্ দেখুৱাওক।}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> আৰু <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ডেস্কটপত এপ্ যোগ দি থকা হৈছে"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"বাতিল কৰক"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"বাবল"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"অ’ভাৰফ্ল’"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>ৰ পৰা <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> আৰু <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> টা"</string>
</resources>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index bb51b42..9cdcc01 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Sancın"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Sərbəst rejim"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Hamısını silin"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Paylaşın"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skrinşot"</string>
<string name="action_split" msgid="2098009717623550676">"Ayırın"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Tətbiq cütünü saxla"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Bölünmüş ekran üçün başqa tətbiqə toxunun"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Bölünmüş ekrandan istifadə üçün başqa tətbiq seçin"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Ləğv edin"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Tapşırıq paneli ilə daha çox şey edin"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"İşləmə panelini həmişə göstərin"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"İşləmə panelini həmişə ekranın aşağısında göstərmək üçün ayırıcı üzərinə toxunun və saxlayın"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Ekranda axtarış etmək üçün fəaliyyət açarına toxunub saxlayın"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Bu məhsul axtarış üçün ekranın seçilmiş hissəsindən istifadə edir. Google <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Məxfilik Siyasəti<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> və <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Xidmət Şərtləri<xliff:g id="END_TOS_LINK"></a></xliff:g> tətbiq edilir."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Bağlayın"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Hazırdır"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Ev"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Yuxarı/sola köçürün"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Aşağı/sağa köçürün"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Daha # tətbiqi göstərin.}other{Daha # tətbiqi göstərin.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# masaüstü tətbiqini göstərin.}other{# masaüstü tətbiqini göstərin.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> və <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Tətbiqin masaüstünə əlavə edilməsi"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Ləğv edin"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Yumrucuq"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Kənara çıxma"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>, <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> və daha <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> yumrucuq"</string>
</resources>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index 5d7652b..b6271a9 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Zakači"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodni oblik"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Računar"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Obriši sve"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Deli"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snimak ekrana"</string>
<string name="action_split" msgid="2098009717623550676">"Podeli"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Sačuvaj par aplikacija"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Dodirnite drugu aplikaciju za podeljeni ekran"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Odaberite drugu aplikaciju da biste koristili podeljeni ekran"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Otkaži"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Uradite više pomoću trake zadataka"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Uvek prikazuj traku zadataka"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Da bi traka zadataka uvek bila prikazana u dnu ekrana, dodirnite i zadržite razdelnik"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Dodirnite i zadržite taster radnji da biste pretražili ono što je na ekranu"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Ovaj proizvod koristi izabrani deo ekrana za pretragu. Primenjuju se Google <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>politika privatnosti<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> i <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>uslovi korišćenja usluge<xliff:g id="END_TOS_LINK"></a></xliff:g>."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Zatvori"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Gotovo"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Početna"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premesti gore levo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premesti dole desno"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Prikaži još # aplikaciju.}one{Prikaži još # aplikaciju.}few{Prikaži još # aplikacije.}other{Prikaži još # aplikacija.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Prikaži # aplikaciju za računare.}one{Prikaži # aplikaciju za računare.}few{Prikaži # aplikacije za računare.}other{Prikaži # aplikacija za računare.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Dodaje se aplikacija na radnu povrršinu"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Otkaži"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Oblačić"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Preklopni"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> i još <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index 646c6d9..0c9851e 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Замацаваць"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Адвольная форма"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Працоўны стол"</string>
<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>
@@ -38,7 +39,7 @@
<string name="hotseat_edu_accept" msgid="1611544083278999837">"Атрымліваць прапановы праграм"</string>
<string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Не, дзякуй"</string>
<string name="hotseat_prediction_settings" msgid="6246554993566070818">"Налады"</string>
- <string name="hotseat_auto_enrolled" msgid="522100018967146807">"Тут з\'яўляюцца праграмы, якімі вы карыстаецеся найбольш часта. Гэты спіс змяняецца на падставе вашых дзеянняў"</string>
+ <string name="hotseat_auto_enrolled" msgid="522100018967146807">"Тут з’яўляюцца праграмы, якімі вы карыстаецеся найбольш часта. Гэты спіс змяняецца на падставе вашых дзеянняў"</string>
<string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"Перацягніце праграмы з ніжняга радка, каб атрымліваць прапановы праграм"</string>
<string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Прапановы праграм дададзены на свабоднае месца"</string>
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Прапановы праграм уключаны"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Абагуліць"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Здымак экрана"</string>
<string name="action_split" msgid="2098009717623550676">"Падзелены экран"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Захаваць спалучэнне"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Каб падзяліць экран, націсніце на іншую праграму"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Каб карыстацца рэжымам падзеленага экрана, выберыце другую праграму"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Скасаваць"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Выкарыстоўвайце магчымасці панэлі задач"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Замацуйце панэль задач унізе экрана"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Для гэтага націсніце на раздзяляльнік і ўтрымлівайце яго"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Для пошуку па змесціве экрана націсніце і ўтрымлівайце клавішу дзеяння"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Гэты прадукт выконвае пошук па змесціве выбранай часткі экрана. Прымяняюцца <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Умовы выкарыстання<xliff:g id="END_TOS_LINK"></a></xliff:g> і <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Палітыка прыватнасці<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Закрыць"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Гатова"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Галоўны экран"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Перамясціць уверх/улева"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Перамясціць уніз/управа"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Паказаць ячшэ # праграму.}one{Паказаць ячшэ # праграму.}few{Паказаць ячшэ # праграмы.}many{Паказаць ячшэ # праграм.}other{Паказаць ячшэ # праграмы.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Паказаць # праграму для ПК.}one{Паказаць # праграму для ПК.}few{Паказаць # праграмы для ПК.}many{Паказаць # праграм для ПК.}other{Паказаць # праграмы для ПК.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> і <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Дадаванне праграмы на камп\'ютар"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Скасаваць"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Бурбалкі"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Меню з пашырэннем"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>, крыніца: <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> і яшчэ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index 59de8de..d674dbc 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Фиксиране"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Свободна форма"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"За компютър"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Споделяне"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Екранна снимка"</string>
<string name="action_split" msgid="2098009717623550676">"Разделяне на екрана"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Запис на двойка приложения"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Докоснете друго прил., за да ползвате разд. екран"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"За разделен екран изберете още едно приложение"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Отказ"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Правете повече неща с лентата на задачите"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Лентата на задачите да се показва винаги"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"За да фиксирате лентата на задачите най-долу на екрана, докоснете и задръжте разделителя"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Докоснете и задръжте клавиша за действия, за да извършите търсене със съдържанието на екрана"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Този продукт използва избраната част на екрана ви, за да търси. Прилагат се <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Декларацията за поверителност<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> и <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Общите условия<xliff:g id="END_TOS_LINK"></a></xliff:g> на Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Затваряне"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Готово"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Начало"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Преместване горе/вляво"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Преместване долу/вдясно"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Показване на още # приложение.}other{Показване на още # приложения.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Показване на # настолно приложение.}other{Показване на # настолни приложения.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Приложението се добавя на настолния компютър"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Отказ"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Балонче"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Препълване"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> от <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> и още <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index 28b1f86..3e974f5 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"পিন করুন"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ফ্রি-ফর্ম"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"শেয়ার করুন"</string>
<string name="action_screenshot" msgid="8171125848358142917">"স্ক্রিনশট নিন"</string>
<string name="action_split" msgid="2098009717623550676">"স্প্লিট"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"অ্যাপ পেয়ার সেভ করুন"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"স্প্লিট স্ক্রিন ব্যবহারের জন্য অ্যাপে ট্যাপ করুন"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"স্প্লিট স্ক্রিন ব্যবহার করতে অন্য অ্যাপ বেছে নিন"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"বাতিল করুন"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"\'টাস্কবার\' ফিচারের সাহায্যে আরও অনেক কিছু করুন"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"টাস্কবার সবসময় দেখানো"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"স্ক্রিনের নিচে টাস্কবার সবসময় দেখাতে ডিভাইডার টাচ করে ধরে থাকুন"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"আপনার স্ক্রিনে দেখতে পাওয়া কন্টেন্ট সার্চ করতে অ্যাকশন কী স্পর্শ করে ধরে রাখুন"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"সার্চ করার জন্য এই প্রোডাক্ট স্ক্রিনের বেছে নেওয়া অংশটুকু ব্যবহার করে। Google-এর <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>গোপনীয়তা নীতি<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> এবং <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>পরিষেবার শর্তাবলী<xliff:g id="END_TOS_LINK"></a></xliff:g> প্রযোজ্য।"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"বন্ধ করুন"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"হয়ে গেছে"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"হোম"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"উপরে/বাঁদিকে সরান"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"নিচে/ডানদিকে সরান"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{আরও #টি অ্যাপ দেখুন।}one{আরও #টি অ্যাপ দেখুন।}other{আরও #টি অ্যাপ দেখুন।}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{#টি ডেস্কটপ অ্যাপ দেখুন।}one{#টি ডেস্কটপ অ্যাপ দেখুন।}other{#টি ডেস্কটপ অ্যাপ দেখুন।}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ও <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ডেস্কটপে অ্যাপ যোগ করা হচ্ছে"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"বাতিল করুন"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"বাবল"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ওভারফ্লো"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> থেকে <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> এবং আরও <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>টি"</string>
</resources>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 549091f..19268be 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Zakači"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodan oblik"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Radna površina"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke korištenja aplikacije"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Obriši sve"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Dijeli"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snimak ekrana"</string>
<string name="action_split" msgid="2098009717623550676">"Podijeli"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Sačuvaj par aplikacija"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Dodirnite drugu apl. da koristite podijeljeni ekran"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Odaberite drugu aplikaciju da koristite podijeljeni ekran"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Otkaži"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Uradite više pomoću trake zadataka"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Stalni prikaz trake zadataka"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Da se traka zadataka uvijek prikazuje na dnu ekrana, dodirnite i zadržite razdjelnik"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Dodirnite i zadržite tipku radnji da pretražite sadržaj na ekranu"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Ovaj proizvod koristi odabrani dio ekrana za pretraživanje. Primjenjuju se Googleova <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Pravila privatnosti<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> i <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Uslovi korištenja usluge<xliff:g id="END_TOS_LINK"></a></xliff:g>."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Zatvori"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Gotovo"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Dom"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premjesti gore lijevo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premjesti dolje desno"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Prikaži još # aplikaciju.}one{Prikaži još # aplikaciju.}few{Prikaži još # aplikacije.}other{Prikaži još # aplikacija.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Prikaži # aplikaciju za računar.}one{Prikaži # aplikaciju za računar.}few{Prikaži # aplikacije za računar.}other{Prikaži # aplikacija za računar.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Dodavanje aplikacije na radnu površinu"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Otkaži"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Oblačić"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Preklopni meni"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> i još <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index c16b64e..7856257 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixa"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format lliure"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escriptori"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Esborra-ho tot"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Comparteix"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Captura de pantalla"</string>
<string name="action_split" msgid="2098009717623550676">"Divideix"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Desa la parella d\'apps"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toca una altra app per utilitzar pantalla dividida"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Tria una altra aplicació per utilitzar la pantalla dividida"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Cancel·la"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Treu més partit de la Barra de tasques"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostra sempre la Barra de tasques"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Perquè es mostri sempre la Barra de tasques a la part inferior de la pantalla, mantén premut el separador"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Mantén premuda la tecla d\'acció per cercar què es mostra a la pantalla"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Aquest producte utilitza la part seleccionada de la pantalla per fer cerques. S\'apliquen la <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>política de privadesa<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> i les <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>condicions del servei<xliff:g id="END_TOS_LINK"></a></xliff:g> de Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Tanca"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Fet"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Inici"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mou a la part superior o a l\'esquerra"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mou a la part inferior o a la dreta"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostra # aplicació més.}other{Mostra # aplicacions més.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostra # aplicació per a ordinadors.}other{Mostra # aplicacions per a ordinadors.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"S\'està afegint l\'aplicació a l\'ordinador"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel·la"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bombolla"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Desbordament"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> i <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> més"</string>
</resources>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 1e58c9c..e134850 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Připnout"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Neomezený režim"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Počítač"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Vymazat vše"</string>
@@ -72,7 +73,7 @@
<string name="overview_gesture_feedback_swipe_too_far_from_edge" msgid="6402349235265407385">"Přejeďte prstem nahoru z dolního okraje obrazovky"</string>
<string name="overview_gesture_feedback_home_detected" msgid="663432226180397138">"Zkuste podržet okno delší dobu, než ho uvolníte"</string>
<string name="overview_gesture_feedback_wrong_swipe_direction" msgid="1191055451018584958">"Přejeďte prstem přímo nahoru a pak udělejte pauzu"</string>
- <string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Naučili jste se používat gesta. Vypnout je můžete v nastavení."</string>
+ <string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Naučili jste se používat gesta. Vypnout je můžete v Nastavení."</string>
<string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Dokončili jste gesto pro přepínání aplikací"</string>
<string name="overview_gesture_intro_title" msgid="2902054412868489378">"Přepínání aplikací přejetím prstem"</string>
<string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Přejeďte nahoru z dolního okraje obrazovky, podržte obrazovku a uvolněte."</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Sdílet"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snímek obrazovky"</string>
<string name="action_split" msgid="2098009717623550676">"Rozdělit"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Uložit dvojici aplikací"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Obrazovku rozdělíte klepnutím na jinou aplikaci"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Výběrem další aplikace rozdělíte obrazovku"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Zrušit"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Více možností s panelem aplikací"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Stálé zobrazení panelu aplikací"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Pokud chcete, aby se panel aplikací vždy zobrazoval ve spodní části obrazovky, podržte oddělovač."</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Podržením akční klávesy můžete vyhledat obsah na obrazovce"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Tato služba používá k vyhledávání vybranou část obrazovky. Platí <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>zásady ochrany soukromí<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> a <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>smluvní podmínky<xliff:g id="END_TOS_LINK"></a></xliff:g> společnosti Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Zavřít"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Hotovo"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Domů"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Přesunout doleva nahoru"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Přesunout doprava dolů"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Zobrazit # další aplikaci.}few{Zobrazit # další aplikace.}many{Zobrazit # další aplikace.}other{Zobrazit # dalších aplikací.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Zobrazit # aplikaci pro počítač.}few{Zobrazit # aplikace pro počítač.}many{Zobrazit # aplikace pro počítač.}other{Zobrazit # aplikací pro počítač.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> a <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Přidání aplikace na plochu"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Zrušit"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bublina"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Rozbalovací nabídka"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> z aplikace <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> a ještě <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index cc534e7..e9cdbce 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fastgør"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Frit format"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Computertilstand"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ingen nye elementer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Indstillinger for appforbrug"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Ryd alt"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Del"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Opdel"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Gem appsammenknytning"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tryk på en anden app for at bruge opdelt skærm"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Vælg en anden app for at bruge opdelt skærm"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Annuller"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Få mere fra hånden med proceslinjen"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Vis altid proceslinjen"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Hvis du vil have, at proceslinjen altid vises nederst på din skærm, skal du holde fingeren på skillelinjen"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Hold fingeren på handlingstasten for at søge efter det, der vises på din skærm"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Dette produkt bruger den valgte del af din skærm til at søge. Googles <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>privatlivspolitik<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> og <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>servicevilkår<xliff:g id="END_TOS_LINK"></a></xliff:g> er gældende."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Luk"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Luk"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Hjem"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flyt til toppen eller venstre side"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flyt til bunden eller højre side"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Vis # app mere.}one{Vis # app mere.}other{Vis # apps mere.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Vis # computerprogram.}one{Vis # computerprogram.}other{Vis # computerprogrammer.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> og <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Appen føjes til computeren"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annuller"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Boble"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overløb"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> fra <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> og <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> mere"</string>
</resources>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index f09e78c..9e5cb12 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixieren"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform-Modus"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopmodus"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Alle Apps schließen"</string>
@@ -94,7 +95,8 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Einstellungen der Systemsteuerung"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Teilen"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
- <string name="action_split" msgid="2098009717623550676">"Teilen"</string>
+ <string name="action_split" msgid="2098009717623550676">"Splitscreen"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"App-Paar speichern"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Für Splitscreen auf weitere App tippen"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Für Splitscreen andere App auswählen"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Abbrechen"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Mehr Möglichkeiten mit der Taskleiste"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Taskleiste immer anzeigen"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Damit die Taskleiste immer unten angezeigt wird, halte den Teiler gedrückt"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Aktionstaste gedrückt halten, um auf dem Bildschirm zu suchen"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Dieses Produkt verwendet den ausgewählten Teil deines Bildschirms für die Suche. Es gelten die <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Datenschutzerklärung<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> und die <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Nutzungsbedingungen<xliff:g id="END_TOS_LINK"></a></xliff:g> von Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Schließen"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Fertig"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Startbildschirm"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Nach oben / Nach links verschieben"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Nach unten / Nach rechts verschieben"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# weitere App anzeigen}other{# weitere Apps anzeigen}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# Desktop-App anzeigen.}other{# Desktop-Apps anzeigen.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> und <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Hinzufügen einer App zum Desktop"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Abbrechen"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Weitere Optionen"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"„<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>“ aus <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> und <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> weitere"</string>
</resources>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index 8c6b0f7..bbb1282 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Καρφίτσωμα"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Ελεύθερη μορφή"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Υπολογιστής"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Κοινοποίηση"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Στιγμιότυπο οθόνης"</string>
<string name="action_split" msgid="2098009717623550676">"Διαχωρισμός"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Αποθήκ. ζεύγ. εφαρμ."</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Πατήστε άλλη εφαρμογή για διαχωρισμό οθόνης"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Επιλέξτε άλλη εφαρμογή για διαχωρισμό οθόνης"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Ακύρωση"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Κάντε περισσότερα με τη Γραμμή εργαλείων"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Να εμφανίζεται πάντα η Γραμμή εργαλείων"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Για να εμφανίζεται πάντα η Γραμμή εργαλείων στο κάτω μέρος της οθόνης, αγγίξτε παρατεταμένα το διαχωριστικό"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Αγγίξτε παρατεταμένα το πλήκτρο ενέργειας για να αναζητήσετε το περιεχόμενο της οθόνης"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Αυτό το προϊόν χρησιμοποιεί το επιλεγμένο τμήμα της οθόνης σας για αναζήτηση. Ισχύουν η <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Πολιτική απορρήτου<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> και οι <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Όροι Παροχής Υπηρεσιών<xliff:g id="END_TOS_LINK"></a></xliff:g> της Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Κλείσιμο"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Τέλος"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Αρχική σελίδα"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Μετακίνηση επάνω/αριστερά"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Μετακίνηση κάτω/δεξιά"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Εμφάνιση # ακόμα εφαρμογής.}other{Εμφάνιση # ακόμα εφαρμογών.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Εμφάνιση # εφαρμογής υπολογιστή.}other{Εμφάνιση # εφαρμογών υπολογιστή.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> και <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Γίνεται προσθήκη εφαρμογής στον υπολογιστή"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Ακύρωση"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Συννεφάκι"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Υπερχείλιση"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> από <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> και <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ακόμα"</string>
</resources>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index 5aa85ca..b84f646 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
@@ -86,7 +87,7 @@
<string name="gesture_tutorial_try_again" msgid="65962545858556697">"Try again"</string>
<string name="gesture_tutorial_nice" msgid="2936275692616928280">"Nice!"</string>
<string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorial <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
- <string name="allset_title" msgid="5021126669778966707">"Ready!"</string>
+ <string name="allset_title" msgid="5021126669778966707">"All set!"</string>
<string name="allset_hint" msgid="459504134589971527">"Swipe up to go home"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Tap the home button to go to your home screen"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"You’re ready to start using your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Share"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Save app pair"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tap another app to use split screen"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choose another app to use split screen"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Cancel"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Do more with the Taskbar"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Always show the Taskbar"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"To always show the Taskbar on the bottom of your screen, touch and hold the divider"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Touch and hold the action key to search what\'s on your screen"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"This product uses the selected part of your screen to search. Google\'s <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Privacy Policy<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> and <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Terms of Service<xliff:g id="END_TOS_LINK"></a></xliff:g> apply."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Close"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Done"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Home"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Show # desktop app.}other{Show # desktop apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adding app to desktop"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> from <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> and <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> more"</string>
</resources>
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index bdc3c22..88cd0dd 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Share"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Save app pair"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tap another app to use split screen"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choose another app to use split screen"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Cancel"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Do more with the Taskbar"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Always show the Taskbar"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"To always show the Taskbar on the bottom of your screen, touch & hold the divider"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Touch & hold the action key to search what\'s on your screen"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"This product uses the selected part of your screen to search. Google\'s <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Privacy Policy<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> and <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Terms of Service<xliff:g id="END_TOS_LINK"></a></xliff:g> apply."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Close"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Done"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Home"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Show # desktop app.}other{Show # desktop apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adding app to Desktop"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> from <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> and <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> more"</string>
</resources>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index 5aa85ca..b84f646 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
@@ -86,7 +87,7 @@
<string name="gesture_tutorial_try_again" msgid="65962545858556697">"Try again"</string>
<string name="gesture_tutorial_nice" msgid="2936275692616928280">"Nice!"</string>
<string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorial <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
- <string name="allset_title" msgid="5021126669778966707">"Ready!"</string>
+ <string name="allset_title" msgid="5021126669778966707">"All set!"</string>
<string name="allset_hint" msgid="459504134589971527">"Swipe up to go home"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Tap the home button to go to your home screen"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"You’re ready to start using your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Share"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Save app pair"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tap another app to use split screen"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choose another app to use split screen"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Cancel"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Do more with the Taskbar"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Always show the Taskbar"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"To always show the Taskbar on the bottom of your screen, touch and hold the divider"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Touch and hold the action key to search what\'s on your screen"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"This product uses the selected part of your screen to search. Google\'s <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Privacy Policy<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> and <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Terms of Service<xliff:g id="END_TOS_LINK"></a></xliff:g> apply."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Close"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Done"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Home"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Show # desktop app.}other{Show # desktop apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adding app to desktop"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> from <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> and <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> more"</string>
</resources>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index 5aa85ca..b84f646 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
@@ -86,7 +87,7 @@
<string name="gesture_tutorial_try_again" msgid="65962545858556697">"Try again"</string>
<string name="gesture_tutorial_nice" msgid="2936275692616928280">"Nice!"</string>
<string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorial <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
- <string name="allset_title" msgid="5021126669778966707">"Ready!"</string>
+ <string name="allset_title" msgid="5021126669778966707">"All set!"</string>
<string name="allset_hint" msgid="459504134589971527">"Swipe up to go home"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Tap the home button to go to your home screen"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"You’re ready to start using your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Share"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Save app pair"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tap another app to use split screen"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choose another app to use split screen"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Cancel"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Do more with the Taskbar"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Always show the Taskbar"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"To always show the Taskbar on the bottom of your screen, touch and hold the divider"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Touch and hold the action key to search what\'s on your screen"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"This product uses the selected part of your screen to search. Google\'s <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Privacy Policy<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> and <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Terms of Service<xliff:g id="END_TOS_LINK"></a></xliff:g> apply."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Close"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Done"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Home"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Show # desktop app.}other{Show # desktop apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adding app to desktop"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> from <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> and <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> more"</string>
</resources>
diff --git a/quickstep/res/values-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml
index 4d87246..76dab0d 100644
--- a/quickstep/res/values-en-rXC/strings.xml
+++ b/quickstep/res/values-en-rXC/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Share"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Save app pair"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tap another app to use split screen"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choose another app to use split screen"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639">""<b>"Cancel"</b>""</string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Do more with the Taskbar"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Always show the Taskbar"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"To always show the Taskbar on the bottom of your screen, touch & hold the divider"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Touch & hold the action key to search what\'s on your screen"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"This product uses the selected part of your screen to search. Google\'s <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Privacy Policy<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> and <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Terms of Service<xliff:g id="END_TOS_LINK"></a></xliff:g> apply."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Close"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Done"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Home"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Show # desktop app.}other{Show # desktop apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adding app to Desktop"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> from <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> and <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> more"</string>
</resources>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 66cf7b9..58d540f 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fijar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Cerrar todo"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Compartir"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Captura de pantalla"</string>
<string name="action_split" msgid="2098009717623550676">"Pantalla dividida"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Guardar vinculación"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Presiona otra app para usar la pantalla dividida"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Elige otra app para usar la pantalla dividida"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Cancelar"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Aprovecha mejor la Barra de tareas"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostrar siempre la Barra de tareas"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Mantén presionado el divisor para mostrar siempre la Barra de tareas en la parte inferior de la pantalla"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Mantén presionada la tecla de acción para buscar qué hay en la pantalla"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este producto usa la parte seleccionada de la pantalla para buscar. Se aplican la <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de Privacidad<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> y las <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Condiciones del Servicio<xliff:g id="END_TOS_LINK"></a></xliff:g> de Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Cerrar"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Listo"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Botón de inicio"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover a la parte superior o izquierda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover a la parte inferior o derecha"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar # app más.}other{Mostrar # apps más.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostrar # app para computadoras.}other{Mostrar # apps para computadoras.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> y <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Agregando app al escritorio"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancelar"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbuja"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Ampliada"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> y <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> más"</string>
</resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 0152b84..7c794f6 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fijar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Borrar todo"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Compartir"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Hacer captura"</string>
<string name="action_split" msgid="2098009717623550676">"Dividir"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Guardar apps emparejadas"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toca otra aplicación para usar la pantalla dividida"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Elige otra app para usar la pantalla dividida."</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Cancelar"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Sácale más partido a la barra de tareas"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostrar siempre la barra de tareas"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Para mostrar siempre la barra de tareas en la parte inferior, mantén pulsada la línea divisoria"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Mantén pulsada la tecla de acción para buscar lo que ves en pantalla"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este producto usa la parte seleccionada de tu pantalla para hacer búsquedas. Se aplican la <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de Privacidad<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> y los <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Términos del Servicio<xliff:g id="END_TOS_LINK"></a></xliff:g> de Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Cerrar"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Hecho"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Inicio"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover arriba/a la izquierda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover abajo/a la derecha"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar # aplicación más.}other{Mostrar # aplicaciones más.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostrar # aplicación para ordenadores.}other{Mostrar # aplicaciones para ordenadores.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> y <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Añadiendo aplicación al ordenador"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancelar"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbuja"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Menú adicional"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> y <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> más"</string>
</resources>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index fb4c8fa..0ecc0c0 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Kinnita"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vabavorm"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Lauaarvuti režiim"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Hiljutisi üksusi pole"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Rakenduse kasutuse seaded"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Sule kõik"</string>
@@ -72,7 +73,7 @@
<string name="overview_gesture_feedback_swipe_too_far_from_edge" msgid="6402349235265407385">"Pühkige kindlasti ekraani alumisest servast üles."</string>
<string name="overview_gesture_feedback_home_detected" msgid="663432226180397138">"Hoidke sõrme aknal pisut kauem, enne kui vabastate"</string>
<string name="overview_gesture_feedback_wrong_swipe_direction" msgid="1191055451018584958">"Pühkige kindlasti otse üles, seejärel peatuge"</string>
- <string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Õppisite liigutusi kasutama. Liigutuste väljalülitamiseks avage seaded."</string>
+ <string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Õppisite liigutusi kasutama. Liigutuste väljalülitamiseks avage Seaded."</string>
<string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Tegite rakenduste vahel vahetamise liigutuse"</string>
<string name="overview_gesture_intro_title" msgid="2902054412868489378">"Pühkige rakenduste vahetamiseks"</string>
<string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Rakenduste vahel vahetamiseks pühkige ekraanikuva alaosast üles, hoidke ja seejärel vabastage."</string>
@@ -89,12 +90,13 @@
<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">"Olete valmis oma seadet <xliff:g id="DEVICE">%1$s</xliff:g> kasutama"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"<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>
<string name="action_screenshot" msgid="8171125848358142917">"Ekraanipilt"</string>
- <string name="action_split" msgid="2098009717623550676">"Eralda"</string>
+ <string name="action_split" msgid="2098009717623550676">"Jaga pooleks"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Salvesta rakendusepaar"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Jagatud ekraanikuva kasutamiseks puudutage muud rakendust"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Valige jagatud ekraanikuva jaoks muu rakendus."</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Tühista"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Tehke tegumiriba abil enamat"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Alati kuvatud tegumiriba"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Puudutage pikalt jaoturit, et tegumiriba oleks ekraani allosas alati kuvatud"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Puudutage pikalt toiminguklahvi, et ekraanil kuvatut otsida"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"See toode kasutab otsingu jaoks ekraani valitud osa. Kehtivad Google\'i <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>privaatsuseeskirjad<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> ja <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>teenusetingimused<xliff:g id="END_TOS_LINK"></a></xliff:g>."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Sule"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Valmis"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Avaleht"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Teisalda üles/vasakule"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Teisalda alla/paremale"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Kuva veel # rakendus.}other{Kuva veel # rakendust.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Kuva # töölauarakendus.}other{Kuva # töölauarakendust.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ja <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Rakenduse lisamine arvutisse"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Tühista"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Mull"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Ületäide"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ja veel <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> mulli"</string>
</resources>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 4fe02c8..e83ee28 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Ainguratu"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Modu librea"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordenagailua"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Garbitu guztiak"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Partekatu"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Atera pantaila-argazki bat"</string>
<string name="action_split" msgid="2098009717623550676">"Zatitu"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Gorde aplikazio parea"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Sakatu beste aplikazio bat pantaila zatitzeko"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Pantaila zatitua erabiltzeko, aukeratu beste aplikazio bat"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Utzi"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Egin gauza gehiago zereginen barrarekin"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Erakutsi beti zereginen barra"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Pantailaren behealdeko zereginen barra beti erakusteko, eduki sakatuta zatitzailea"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Eduki sakatuta ekintza-tekla pantailan dagoena bilatzeko"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Produktu honek pantailan hautatutako zatia erabiltzen du bilaketa egiteko. Google-ren <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Pribatutasun-gidalerroak<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> eta <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Zerbitzu-baldintzak<xliff:g id="END_TOS_LINK"></a></xliff:g> aplikatzen dira."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Itxi"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Eginda"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Hasiera"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Eraman gora, ezkerretara"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Eraman behera, eskuinetara"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Erakutsi beste # aplikazio.}other{Erakutsi beste # aplikazio.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Erakutsi ordenagailuetarako # aplikazio.}other{Erakutsi ordenagailuetarako # aplikazio.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> eta <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Aplikazioa mahaigainean gehitzen"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Utzi"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbuila"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Luzapena"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> (<xliff:g id="APP_NAME">%2$s</xliff:g>)"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> eta beste <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index 3324805..64e3e1e 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"پین"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"حالت رایانه"</string>
<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>
@@ -88,13 +89,14 @@
<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_button_hint" msgid="2395219947744706291">"برای رفتن به صفحه اصلی، روی دکمه صفحه اصلی ضربه بزنید"</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>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"تنظیمات پیمایش سیستم"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"همرسانی"</string>
<string name="action_screenshot" msgid="8171125848358142917">"نماگرفت"</string>
<string name="action_split" msgid="2098009717623550676">"دونیمه"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ذخیره جفت برنامه"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"زدن روی برنامهای دیگر برای استفاده از صفحه دونیمه"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"انتخاب برنامهای دیگر برای استفاده از صفحه دونیمه"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"لغو کردن"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"با «نوار وظیفه» میتوانید کارهای بیشتر انجام دهید"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"همیشه نشان داده شدن «نوار وظیفه»"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"برای اینکه «نوار وظیفه» همیشه در پایین صفحه نشان داده شود، تقسیمکننده را لمس کنید و نگه دارید"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"برای جستجوی محتوای صفحهنمایش، دکمه کنش را لمس کنید و نگه دارید"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"این محصول از قسمت انتخابشده صفحهنمایش شما برای جستجو استفاده میکند. <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>خطمشی رازداری<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> و <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>شرایط خدمات<xliff:g id="END_TOS_LINK"></a></xliff:g> Google اعمال میشود."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"بستن"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"تمام"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"صفحه اصلی"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"انتقال به بالا/ چپ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"انتقال به پایین/ راست"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{نمایش # برنامه دیگر.}one{نمایش # برنامه دیگر.}other{نمایش # برنامه دیگر.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{نمایش # برنامه رایانه.}one{نمایش # برنامه رایانه.}other{نمایش # برنامه رایانه.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> و <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"درحال افزودن برنامه به رایانه"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"لغو"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"حبابک"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"سرریز"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> و <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> حبابک دیگر"</string>
</resources>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index b92ef05..5ac124a 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Kiinnitä"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vapaamuotoinen"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Tietokone"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ei viimeaikaisia kohteita"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Sovelluksen käyttöasetukset"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Poista kaikki"</string>
@@ -94,7 +95,8 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Järjestelmän navigointiasetukset"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Jaa"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Kuvakaappaus"</string>
- <string name="action_split" msgid="2098009717623550676">"Jaa"</string>
+ <string name="action_split" msgid="2098009717623550676">"Jaettu näyttö"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Tallenna pari"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Avaa jaettu näyttö napauttamalla toista sovellusta"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Käytä jaettua näyttöä valitsemalla toinen sovellus"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Peruuta"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Vinkkejä tehtäväpalkin tehokkaampaan käyttöön"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Näytä tehtäväpalkki aina"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Jos haluat tehtäväpalkin näkyvän aina näytön alaosassa, kosketa jakajaa pitkään"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Kosketa toimintonäppäintä pitkään, niin voit hakea näytöltä"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Tämä tuote käyttää hakuun valittua näytön osaa. Tähän sovelletaan Googlen <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>tietosuojakäytäntöä<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> ja <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>käyttöehtoja<xliff:g id="END_TOS_LINK"></a></xliff:g>."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Sulje"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Valmis"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Etusivu"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Siirrä ylös tai vasemmalle"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Siirrä alas tai oikealle"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Näytä # muu sovellus.}other{Näytä # muuta sovellusta.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Näytä # työpöytäsovellus.}other{Näytä # työpöytäsovellusta.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ja <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Sovelluksen lisääminen työpöydälle"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Peru"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Kupla"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Ylivuoto"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ja <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> muuta"</string>
</resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index 68ca935..9510494 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -21,35 +21,36 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Épingler"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forme libre"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordinateur de bureau"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
- <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'application"</string>
+ <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'appli"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Tout effacer"</string>
- <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applications récentes"</string>
+ <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applis récentes"</string>
<string name="task_view_closed" msgid="9170038230110856166">"Tâche fermée"</string>
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> : <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
<string name="time_left_for_app" msgid="3111996412933644358">"Il reste <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
- <string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applications"</string>
- <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos prédictions d\'applications"</string>
- <string name="hotseat_edu_title_migrate" msgid="306578144424489980">"Obtenir des suggestions d\'applications dans la rangée du bas de votre écran d\'accueil"</string>
- <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Retrouvez des suggestions d\'applications dans la rangée des favoris de votre écran d\'accueil"</string>
- <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée du bas seront déplacées vers votre écran d\'accueil."</string>
- <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée des favoris seront déplacées vers votre écran d\'accueil."</string>
- <string name="hotseat_edu_accept" msgid="1611544083278999837">"Obtenir des suggestions d\'applications"</string>
+ <string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applis"</string>
+ <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos prédictions d\'applis"</string>
+ <string name="hotseat_edu_title_migrate" msgid="306578144424489980">"Obtenir des suggestions d\'applis dans la rangée du bas de votre écran d\'accueil"</string>
+ <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Retrouvez des suggestions d\'applis dans la rangée des favoris de votre écran d\'accueil"</string>
+ <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accédez facilement aux applis que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applis dans la rangée du bas seront déplacées vers votre écran d\'accueil."</string>
+ <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accédez facilement aux applis que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applis dans la rangée des favoris seront déplacées vers votre écran d\'accueil."</string>
+ <string name="hotseat_edu_accept" msgid="1611544083278999837">"Obtenir des suggestions d\'applis"</string>
<string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Non merci"</string>
<string name="hotseat_prediction_settings" msgid="6246554993566070818">"Paramètres"</string>
- <string name="hotseat_auto_enrolled" msgid="522100018967146807">"Les applications les plus utilisées s\'affichent ici et changent en fonction des habitudes"</string>
- <string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"Faites glisser des applications hors de la rangée du bas pour obtenir des suggestions d\'applications"</string>
- <string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Applications suggérées ajoutées à l\'espace vide"</string>
- <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Les suggestions d\'applications sont activées"</string>
- <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Les suggestions d\'applications sont désactivées"</string>
- <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Application prédite : <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+ <string name="hotseat_auto_enrolled" msgid="522100018967146807">"Les applis les plus utilisées s\'affichent ici et changent en fonction des habitudes"</string>
+ <string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"Faites glisser des applis hors de la rangée du bas pour obtenir des suggestions d\'applis"</string>
+ <string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Applis suggérées ajoutées à l\'espace vide"</string>
+ <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Les suggestions d\'applis sont activées"</string>
+ <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Les suggestions d\'applis sont désactivées"</string>
+ <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Appli prédite : <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Faites pivoter votre appareil"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Veuillez faire pivoter votre appareil pour terminer le tutoriel de navigation par gestes"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assurez-vous de balayer l\'écran à partir de l\'extrémité droite ou gauche"</string>
<string name="back_gesture_feedback_cancelled" msgid="762621530959111290">"Assurez-vous de balayer l\'écran à partir de l\'extrémité droite ou gauche vers le centre, puis allons-y"</string>
<string name="back_gesture_feedback_complete_with_overview_follow_up" msgid="9176400654037014471">"Vous avez appris à balayer de la droite pour revenir en arrière. Apprenez comment changer d\'appli."</string>
- <string name="back_gesture_feedback_complete_with_follow_up" msgid="8653374779579748392">"Vous avez appris le geste de retour en arrière. Maintenant, apprenez comment changer d\'application."</string>
+ <string name="back_gesture_feedback_complete_with_follow_up" msgid="8653374779579748392">"Vous avez appris le geste de retour en arrière. Maintenant, apprenez comment changer d\'appli."</string>
<string name="back_gesture_feedback_complete_without_follow_up" msgid="197189945858268342">"Vous avez appris le geste de retour en arrière"</string>
<string name="back_gesture_feedback_swipe_in_nav_bar" msgid="9157480023651452969">"Assurez-vous de ne pas balayer trop près du bas de l\'écran"</string>
<string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Modifiez la sensibilité du geste de retour dans Paramètres"</string>
@@ -73,11 +74,11 @@
<string name="overview_gesture_feedback_home_detected" msgid="663432226180397138">"Essayez de tenir la fenêtre plus longtemps avant de relâcher"</string>
<string name="overview_gesture_feedback_wrong_swipe_direction" msgid="1191055451018584958">"Assurez-vous de balayer l\'écran vers le haut, puis de faire une pause"</string>
<string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Vous avez appris à utiliser les gestes. Pour les désactiver, accédez au menu Paramètres."</string>
- <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Vous avez appris le geste de changement d\'application"</string>
- <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Balayez pour basculer entre les applications"</string>
- <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Pour changer d\'application, balayez l\'écran de bas en haut, maintenez le doigt dessus, puis relâchez-le."</string>
+ <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Vous avez appris le geste de changement d\'appli"</string>
+ <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Balayez pour basculer entre les applis"</string>
+ <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Pour changer d\'appli, balayez l\'écran de bas en haut, maintenez le doigt dessus, puis relâchez-le."</string>
<string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Pour changer d\'appli, balayez l\'écran de bas en haut avec deux doigts, maintenez-les et relâchez-les."</string>
- <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Changer d\'application"</string>
+ <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Changer d\'appli"</string>
<string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Balayez l\'écran de bas en haut, maintenez le doigt en place, puis relâchez-le"</string>
<string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Bien joué!"</string>
<string name="gesture_tutorial_confirm_title" msgid="6201516182040074092">"Terminé"</string>
@@ -95,15 +96,16 @@
<string name="action_share" msgid="2648470652637092375">"Partager"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Capture d\'écran"</string>
<string name="action_split" msgid="2098009717623550676">"Partager"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Enr. paire d\'applis"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toucher une autre appli pour partager l\'écran"</string>
- <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choisir une autre application pour utiliser l\'Écran divisé"</string>
+ <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choisir une autre appli pour utiliser l\'Écran divisé"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Annuler"</b></string>
<string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Quitter la sélection d\'écran divisé"</string>
- <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choisir une autre application pour utiliser l\'écran partagé"</string>
- <string name="blocked_by_policy" msgid="2071401072261365546">"L\'application ou votre organisation n\'autorise pas cette action"</string>
- <string name="split_widgets_not_supported" msgid="1355743038053053866">"Les widgets ne sont actuellement pas pris en charge. Veuillez sélectionner une autre application"</string>
+ <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choisir une autre appli pour utiliser l\'écran partagé"</string>
+ <string name="blocked_by_policy" msgid="2071401072261365546">"L\'appli ou votre organisation n\'autorise pas cette action"</string>
+ <string name="split_widgets_not_supported" msgid="1355743038053053866">"Les widgets ne sont actuellement pas pris en charge. Veuillez sélectionner une autre appli"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Ignorer le tutoriel sur la navigation?"</string>
- <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Vous trouverez le tutoriel dans l\'application <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Vous trouverez le tutoriel dans l\'appli <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Annuler"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Ignorer"</string>
<string name="accessibility_rotate_button" msgid="4771825231336502943">"Faire pivoter l\'écran"</string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Faites-en plus avec la barre des tâches"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Toujours afficher la Barre des tâches"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Pour toujours afficher la Barre des tâches en bas de l\'écran, maintenez le doigt sur le séparateur"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Maintenez le doigt sur la touche d\'action pour rechercher ce qui se trouve sur votre écran"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Ce produit utilise la partie sélectionnée de votre écran pour effectuer une recherche. La <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>politique de confidentialité<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> et les <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>conditions d\'utilisation<xliff:g id="END_TOS_LINK"></a></xliff:g> de Google s\'appliquent."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Fermer"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"OK"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Accueil"</string>
@@ -133,8 +137,11 @@
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Séparateur de la barre des tâches"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer vers le coin supérieur gauche de l\'écran"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer vers le coin inférieur droit de l\'écran"</string>
- <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre application.}one{Afficher # autre application.}other{Afficher # autres applications.}}"</string>
+ <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre appli.}one{Afficher # autre appli.}other{Afficher # autres applis.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Afficher # appli de bureau.}one{Afficher # appli de bureau.}other{Afficher # applis de bureau.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> et <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Ajout de l\'application au bureau en cours…"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annuler"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bulle"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Bulle à développer"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> et <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> autres"</string>
</resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index b554a48..60f8944 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Épingler"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format libre"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordinateur"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Tout effacer"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Partager"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Capture d\'écran"</string>
<string name="action_split" msgid="2098009717623550676">"Partager"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Enregistrer la paire d\'applis"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Appuyez sur autre appli pour l\'écran partagé"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Sélectionnez une autre appli pour utiliser l\'écran partagé."</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Annuler"</b></string>
@@ -109,12 +111,14 @@
<string name="accessibility_rotate_button" msgid="4771825231336502943">"Faire pivoter l\'écran"</string>
<string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"Fonctionnement de la barre des tâches"</string>
<string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Faites glisser une appli sur le côté pour en utiliser 2 à la fois"</string>
- <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Balayez lentement vers haut pour afficher barre des tâches"</string>
+ <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Balayez lentement vers le haut pour voir la barre des tâches"</string>
<string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Obtenez des suggestions d\'applis basées sur vos habitudes"</string>
<string name="taskbar_edu_pinning" msgid="6708550858580071558">"Appui prolongé sur le séparateur pour épingler la barre des tâches"</string>
<string name="taskbar_edu_features" msgid="3320337287472848162">"Faites-en plus avec la barre des tâches"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Toujours afficher la barre des tâches"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Pour toujours afficher la barre des tâches en bas de votre écran, appuyez sur le séparateur de manière prolongée."</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Appuyez de manière prolongée sur la touche d\'action pour rechercher ce qui se trouve à l\'écran"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Ce produit utilise la zone sélectionnée de l\'écran pour rechercher. Les <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Règles de confidentialité<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> et les <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Conditions d\'utilisation<xliff:g id="END_TOS_LINK"></a></xliff:g> de Google s\'appliquent."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Fermer"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"OK"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Accueil"</string>
@@ -133,8 +137,11 @@
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Séparateur de barre des tâches"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer en haut ou à gauche"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer en bas ou à droite"</string>
- <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre appli.}one{Afficher # autre appli.}other{Afficher # autre applis.}}"</string>
+ <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre appli}one{Afficher # autre appli}other{Afficher # autre applis}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Afficher # application de bureau.}one{Afficher # application de bureau.}other{Afficher # applications de bureau.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> et <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Ajout de l\'appli au bureau"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annuler"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bulle"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Dépassement"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> (<xliff:g id="APP_NAME">%2$s</xliff:g>)"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> et <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> autre(s)"</string>
</resources>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index 2207f17..bf081d4 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libre"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Borrar todo"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Compartir"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Facer captura"</string>
<string name="action_split" msgid="2098009717623550676">"Dividir"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Gardar parella apps"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Para usar a pantalla dividida, toca outra app"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Escolle outra aplicación para usar a pantalla dividida."</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Cancelar"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Tira máis proveito da barra de tarefas"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostrar sempre a barra de tarefas"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Para fixar a barra de tarefas na parte inferior, mantén premida a liña divisoria"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Mantén premida a tecla de acción para buscar o que hai na pantalla"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este produto utiliza a parte seleccionada da pantalla para facer buscas. Aplícanse as <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Condicións de servizo<xliff:g id="END_TOS_LINK"></a></xliff:g> e a <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de privacidade<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> de Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Pechar"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Listo"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Inicio"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover á parte superior ou á esquerda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover á parte inferior ou á dereita"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar # aplicación máis.}other{Mostrar # aplicacións máis.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostrar # aplicación para ordenadores.}other{Mostrar # aplicacións para ordenadores.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Engadindo aplicación ao ordenador"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancelar"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbulla"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Menú adicional"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> e <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> máis"</string>
</resources>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index f754675..b20f771 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"પિન કરો"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ફ્રિફોર્મ"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ડેસ્કટૉપ"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"શેર કરો"</string>
<string name="action_screenshot" msgid="8171125848358142917">"સ્ક્રીનશૉટ"</string>
<string name="action_split" msgid="2098009717623550676">"વિભાજિત કરો"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ઍપની જોડી સાચવો"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"વિભાજિત સ્ક્રીન વાપરવા, કોઈ અન્ય ઍપ પર ટૅપ કરો"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"વિભાજિત સ્ક્રીનની સુવિધાનો ઉપયોગ કરવા કોઈ અન્ય ઍપ પસંદ કરો"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"રદ કરો"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"ટાસ્કબાર વડે બીજું ઘણું કરો"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"ટાસ્કબાર હંમેશાં બતાવો"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"ટાસ્કબાર હંમેશાં તમારી સ્ક્રીનમાં સૌથી નીચે દેખાય તે માટે વિભાજકને ટચ કરીને થોડીવાર દબાવી રાખો"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"તમારી સ્ક્રીન પર જે હોય તે શોધવા માટે, ઍક્શન કી ટચ કરીને દબાવી રાખો"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"શોધવા માટે, આ પ્રોડક્ટ તમારી સ્ક્રીનના પસંદ કરેલા ભાગનો ઉપયોગ કરે છે. Googleની <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>પ્રાઇવસી પૉલિસી<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> અને <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>સેવાની શરતો<xliff:g id="END_TOS_LINK"></a></xliff:g> લાગુ થાય છે."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"બંધ કરો"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"થઈ ગયું"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"હોમ"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"સૌથી ઉપર ડાબી બાજુએ ખસેડો"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"સૌથી નીચે જમણી બાજુએ ખસેડો"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{વધુ # ઍપ બતાવો.}one{વધુ # ઍપ બતાવો.}other{વધુ # ઍપ બતાવો.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ડેસ્કટૉપ ઍપ બતાવો.}one{# ડેસ્કટૉપ ઍપ બતાવો.}other{# ડેસ્કટૉપ ઍપ બતાવો.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> અને <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ડેસ્કટૉપ પર ઍપ ઉમેરી રહ્યાં છીએ"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"રદ કરો"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"બબલ"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ઓવરફ્લો"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>થી <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> અને વધુ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 51351cb..a645186 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"पिन करें"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ़्रीफ़ॉर्म"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"शेयर करें"</string>
<string name="action_screenshot" msgid="8171125848358142917">"स्क्रीनशॉट लें"</string>
<string name="action_split" msgid="2098009717623550676">"स्प्लिट स्क्रीन मोड"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ऐप पेयर सेव करें"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"स्प्लिट स्क्रीन के लिए दूसरे ऐप्लिकेशन पर टैप करें"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"स्प्लिट स्क्रीन इस्तेमाल करने के लिए, दूसरा ऐप्लिकेशन चुनें"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"अभी नहीं"</b></string>
@@ -110,11 +112,13 @@
<string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"टास्कबार का ट्यूटोरियल"</string>
<string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"किसी ऐप को किनारे की ओर ड्रैग करके 2 ऐप एक साथ इस्तेमाल करें"</string>
<string name="taskbar_edu_stashing" msgid="5645461372669217294">"टास्कबार देखने के लिए, ऊपर की ओर धीरे से स्वाइप करें"</string>
- <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"डिवाइस के इस्तेमाल के आधार पर ऐप्लिकेशन के सुझाव पाएं"</string>
+ <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"इस्तेमाल के आधार पर ऐप्लिकेशन के सुझाव पाएं"</string>
<string name="taskbar_edu_pinning" msgid="6708550858580071558">"टास्कबार को पिन करने के लिए डिवाइडर को दबाकर रखें"</string>
- <string name="taskbar_edu_features" msgid="3320337287472848162">"टास्कबार की मदद से कई और काम करें"</string>
+ <string name="taskbar_edu_features" msgid="3320337287472848162">"टास्कबार के साथ कई और काम करें"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"टास्कबार को हमेशा दिखाएं"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"टास्कबार को हमेशा अपनी स्क्रीन के नीचे दिखाने के लिए, डिवाइडर दबाकर रखें"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"स्क्रीन पर दिख रहे कॉन्टेंट को खोजने के लिए, ऐक्शन बटन को दबाकर रखें"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"यह प्रॉडक्ट, स्क्रीन के चुनिंदा हिस्से पर कॉन्टेंट खोजता है. Google की <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>निजता नीति<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> और <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>सेवा की शर्तें<xliff:g id="END_TOS_LINK"></a></xliff:g> लागू."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"बंद करें"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"हो गया"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"होम"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ऊपर/बाईं तरफ़ ले जाएं"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"नीचे/दाईं तरफ़ ले जाएं"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# और ऐप्लिकेशन दिखाएं.}one{# और ऐप्लिकेशन दिखाएं.}other{# और ऐप्लिकेशन दिखाएं.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# डेस्कटॉप ऐप्लिकेशन दिखाएं.}one{# डेस्कटॉप ऐप्लिकेशन दिखाएं.}other{# डेस्कटॉप ऐप्लिकेशन दिखाएं.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> और <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"डेस्कटॉप पर ऐप्लिकेशन जोड़ा जा रहा है"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"रद्द करें"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"बबल"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ओवरफ़्लो"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> की <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> वाली सूचना"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> और <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> अन्य"</string>
</resources>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index 26ae082..c96381d 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Prikvači"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodni oblik"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Računalo"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke upotrebe aplikacija"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Izbriši sve"</string>
@@ -89,12 +90,13 @@
<string name="allset_title" msgid="5021126669778966707">"Sve je spremno!"</string>
<string name="allset_hint" msgid="459504134589971527">"Prijeđite prstom prema gore da biste otvorili početni zaslon"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Dodirnite gumb početnog zaslona da biste prešli na početni zaslon"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"Spremni ste za početak upotrebe uređaja <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> je spreman za početak upotrebe"</string>
<string name="default_device_name" msgid="6660656727127422487">"uređaj"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Postavke navigacije sustavom"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Podijeli"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snimka zaslona"</string>
- <string name="action_split" msgid="2098009717623550676">"Podijeli"</string>
+ <string name="action_split" msgid="2098009717623550676">"Podjela"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Spremi par apl."</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Dodirnite drugu aplikaciju za podijeljeni zaslon"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Odaberite drugu aplikaciju za upotrebu podijeljenog zaslona"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Odustani"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Učinite više pomoću trake sa zadacima"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Uvijek prikazuj traku sa zadacima"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Da bi se traka prikazivala, dodirnite i držite razdjelnik"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Dodirnite i zadržite tipku za radnju da biste pretražili što se nalazi na zaslonu"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Ovaj proizvod upotrebljava odabrani dio zaslona za pretraživanje. Primjenjuju se Googleova <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>pravila o privatnosti<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> i <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>uvjeti pružanja usluge<xliff:g id="END_TOS_LINK"></a></xliff:g>."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Zatvori"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Gotovo"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Početna"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premjesti gore/lijevo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premjesti dolje/desno"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Prikaži još # aplikaciju}one{Prikaži još # aplikaciju}few{Prikaži još # aplikacije}other{Prikaži još # aplikacija}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Prikaži # računalnu aplikaciju.}one{Prikaži # računalnu aplikaciju.}few{Prikaži # računalne aplikacije.}other{Prikaži # računalnih aplikacija.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Dodavanje aplikacije na radnu površinu"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Odustani"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Oblačić"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Dodatni izbornik"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>, <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> i još <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 7ea486d..03235aa 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Kitűzés"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Szabad forma"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Asztali"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Összes törlése"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Megosztás"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Képernyőkép"</string>
<string name="action_split" msgid="2098009717623550676">"Felosztás"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"App-pár mentése"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Koppintson másik appra az osztott képernyőhöz"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Válasszon másik appot a képernyő felosztásához"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Mégse"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Jobban kihasználhatja a Feladatsávot"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mindig jelenjen meg a Feladatsáv"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Ahhoz, hogy a Feladatsáv mindig megjelenjen a képernyő alján, érintse meg és tartsa lenyomva az elválasztót"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"A műveletbillentyűt lenyomva tartva kereshet a képernyőn található tartalmak között"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Ez a termék a képernyő kiválasztott részét használja a kereséshez. A Google <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Adatvédelmi irányelvei<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> és <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Általános Szerződési Feltételei<xliff:g id="END_TOS_LINK"></a></xliff:g> érvényesek."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Bezárás"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Kész"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Kezdőlap"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mozgatás felülre vagy a bal oldalra"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mozgatás alulra vagy a jobb oldalra"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# további alkalmazás megjelenítése.}other{# további alkalmazás megjelenítése.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# asztali alkalmazás megjelenítése.}other{# asztali alkalmazás megjelenítése.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> és <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Alkalmazás hozzáadása az asztalhoz"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Mégse"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Buborék"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Túlcsordulás"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>, forrás: <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> és <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> további"</string>
</resources>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index 367d378..3cb7990 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Ամրացնել"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Կամայական ձև"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Համակարգիչ"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Կիսվել"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Սքրինշոթ անել"</string>
<string name="action_split" msgid="2098009717623550676">"Տրոհել"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Պահել հավելվ․ զույգը"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Հպեք այլ հավելվածի՝ տրոհված էկրանից օգտվելու համար"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Ընտրեք այլ հավելված՝ տրոհված էկրանից օգտվելու համար"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Չեղարկել"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Օգտվեք հավելվածների վահանակի բոլոր հնարավորություններից"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Ամրացրեք հավելվածների վահանակը"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Հավելվածների վահանակն էկրանի ներքևում ամրացնելու համար հպեք և պահեք բաժանիչը"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Սեղմած պահեք գործողության ստեղնը՝ էկրանին բովանդակություն որոնելու համար"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Այս պրոդուկտն օգտագործում է էկրանի ընտրված հատվածը որոնման համար։ Կիրառվում են Google-ի <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Գաղտնիության քաղաքականությունը<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> և <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Օգտագործման պայմանները<xliff:g id="END_TOS_LINK"></a></xliff:g>։"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Փակել"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Պատրաստ է"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Սկիզբ"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Տեղափոխել վերևի ձախ անկյուն"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Տեղափոխել ներքևի աջ անկյուն"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Ցուցադրել ևս # հավելված։}one{Ցուցադրել ևս # հավելված։}other{Ցուցադրել ևս # հավելված։}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Ցույց տալ # համակարգչային հավելված։}one{Ցույց տալ # համակարգչային հավելված։}other{Ցույց տալ # համակարգչային հավելված։}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> և <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Հավելվածն ավելացվում է աշխատասեղանին"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Չեղարկել"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Ամպիկ"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Լրացուցիչ ընտրացանկ"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>՝ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածից"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ու ևս <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ամպիկ"</string>
</resources>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 6208796..015b09e 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Sematkan"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format bebas"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Hapus semua"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Bagikan"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Pisahkan"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Simpan pasangan apl"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Ketuk aplikasi lain untuk memakai layar terpisah"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Pilih aplikasi lain untuk dibuka di layar terpisah"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Batal"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Lakukan lebih banyak hal dengan Taskbar"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Selalu tampilkan Taskbar"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Untuk selalu menampilkan Taskbar di bagian bawah layar Anda, sentuh & tahan pembatasnya"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Sentuh & tahan tombol tindakan untuk mencari konten di layar Anda"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Produk ini menggunakan bagian layar terpilih untuk menelusuri. <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Kebijakan Privasi<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> dan <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Persyaratan Layanan<xliff:g id="END_TOS_LINK"></a></xliff:g> Google berlaku."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Tutup"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Selesai"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Layar utama"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Pindahkan ke atas/kiri"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pindahkan ke bawah/kanan"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Tampilkan # aplikasi lainnya.}other{Tampilkan # aplikasi lainnya.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Tampilkan # aplikasi desktop.}other{Tampilkan # aplikasi desktop.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> dan <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Menambahkan aplikasi ke Desktop"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Batalkan"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Balon"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Tambahan"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> dari <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> dan <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> lainnya"</string>
</resources>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 09249bc..883fe82 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Festa"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Frjálst snið"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Tölva"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Engin nýleg atriði"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Notkunarstillingar forrits"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Hreinsa allt"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Deila"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skjámynd"</string>
<string name="action_split" msgid="2098009717623550676">"Skipta"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Vista forritapar"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Ýttu á annað forrit til að nota skjáskiptingu"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Veldu annað forrit til að nota skjáskiptingu"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Hætta við"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Nýttu forritastikuna betur"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Halda forritastikunni sýnilegri"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Haltu skjáskiptingunni neðst á skjánum inni til að halda forritastikunni sýnilegri"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Haltu aðgerðalyklinum inni til að leita að því sem er á skjánum"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Þessi vara notar valinn hluta skjásins til að leita. <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Persónuverndarstefna<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> og <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>þjónustuskilmálar<xliff:g id="END_TOS_LINK"></a></xliff:g> Google gilda."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Loka"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Lokið"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Heim"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Færa efst/til vinstri"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Færa neðst/til hægri"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Sýna # forrit í viðbót.}one{Sýna # forrit í viðbót.}other{Sýna # forrit í viðbót.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Sýna # skjáborðsforrit.}one{Sýna # skjáborðsforrit.}other{Sýna # skjáborðsforrit.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> og <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Forriti bætt við skjáborð"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Hætta við"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Blaðra"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Yfirflæði"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> frá <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> og <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> í viðbót"</string>
</resources>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index 961a2ff..59b195a 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Blocca su schermo"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libera"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Cancella tutto"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Condividi"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Dividi"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Salva coppia di app"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tocca un\'altra app per usare lo schermo diviso"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Scegli un\'altra app per usare lo schermo diviso"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Annulla"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Fai di più con la barra delle app"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostra sempre la barra delle app"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Per mostrare sempre la barra delle app in basso, tocca e tieni premuto il divisore"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Tocca e tieni premuto il tasto azione per cercare gli elementi sullo schermo"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Il prodotto usa la parte selezionata dello schermo per cercare. Si applicano le <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Norme sulla privacy<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> e i <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Termini di servizio<xliff:g id="END_TOS_LINK"></a></xliff:g> di Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Chiudi"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Fine"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Home"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Sposta in alto/a sinistra"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sposta in basso/a destra"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostra # altra app.}other{Mostra altre # app.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostra # app desktop.}other{Mostra # app desktop.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Aggiunta app a desktop in corso…"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annulla"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Fumetto"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Extra"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> da <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> e altri <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index f84db18..9f0ed14 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"הצמדה"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"מצב חופשי"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"במחשב"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"שיתוף"</string>
<string name="action_screenshot" msgid="8171125848358142917">"צילום מסך"</string>
<string name="action_split" msgid="2098009717623550676">"פיצול"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"שמירת צמד אפליקציות"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"צריך להקיש על אפליקציה אחרת כדי להשתמש במסך מפוצל"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"כדי להשתמש במסך מפוצל צריך לבחור אפליקציה אחרת"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"ביטול"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"פעולות נוספות שאפשר לעשות עם סרגל האפליקציות"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"תמיד להציג את סרגל האפליקציות"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"כדי להציג תמיד את סרגל האפליקציות בתחתית המסך, יש ללחוץ לחיצה ארוכה על המחיצה"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"כדי לחפש במסך, צריך ללחוץ לחיצה ארוכה על מקש הפעולה"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"המוצר הזה משתמש בחלק שבחרת במסך לצורך חיפוש, בכפוף ל<xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>מדיניות הפרטיות<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> ו<xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>לתנאים ולהגבלות<xliff:g id="END_TOS_LINK"></a></xliff:g> של Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"סגירה"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"סיום"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"בית"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"העברה לפינה השמאלית/העליונה"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"העברה לפינה הימנית/התחתונה"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{הצגת אפליקציה אחת (#) נוספת.}one{הצגת # אפליקציות נוספות.}two{הצגת # אפליקציות נוספות.}other{הצגת # אפליקציות נוספות.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{הצגת אפליקציה אחת (#) למחשב.}one{הצגת # אפליקציות למחשב.}two{הצגת # אפליקציות למחשב.}other{הצגת # אפליקציות למחשב.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ו-<xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"האפליקציה מתווספת לשולחן העבודה"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ביטול"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"בועה"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"אפשרויות נוספות"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> מתוך <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ועוד <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index 6b555c1..6b445a1 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"フリーフォーム"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"デスクトップ"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"共有"</string>
<string name="action_screenshot" msgid="8171125848358142917">"スクリーンショット"</string>
<string name="action_split" msgid="2098009717623550676">"分割"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"アプリのペア設定保存"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"分割画面を使用するには、他のアプリをタップします"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"分割画面を使用するには別のアプリを選択してください"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"キャンセル"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"タスクバーの各種機能"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"タスクバーを常に表示"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"タスクバーを画面下部に常に表示するには分割線を長押しします"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"画面上の内容を検索するには、アクションキーを長押ししてください"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"このサービスは、検索する際に画面上で選択された箇所を使用します。Google の<xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>プライバシー ポリシー<xliff:g id="END_PRIVACY_LINK"></a></xliff:g>と<xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>利用規約<xliff:g id="END_TOS_LINK"></a></xliff:g>が適用されます。"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"閉じる"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"完了"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"ホーム"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"上 / 左に移動"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"下 / 右に移動"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{他 # 件のアプリを表示できます。}other{他 # 件のアプリを表示できます。}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# 個のデスクトップ アプリが表示されます。}other{# 個のデスクトップ アプリが表示されます。}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> と <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"アプリをデスクトップに追加する"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"キャンセル"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ふきだし"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"オーバーフロー"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>(<xliff:g id="APP_NAME">%2$s</xliff:g>)"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>、他 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> 件"</string>
</resources>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index 13d2445..1fb6077 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"ჩამაგრება"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"თავისუფალი ფორმა"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"დესკტოპი"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"გაზიარება"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ეკრანის ანაბეჭდი"</string>
<string name="action_split" msgid="2098009717623550676">"გაყოფა"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"აპთა წყვილის შენახვა"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"შეეხეთ სხვა აპს ეკრანის გასაყოფად"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"აირჩიეთ სხვა აპი ეკრანის გასაყოფად"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"გაუქმება"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"გააკეთეთ მეტი ამოცანათა ზოლის მეშვეობით"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"ამოცანათა ზოლის მუდმივად ჩვენება"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"თქვენი ეკრანის ქვედა ნაწილში ამოცანათა ზოლის მუდმივად საჩვენებლად, ხანგრძლივად შეეხეთ გამყოფს"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"შეეხეთ და გეჭიროთ მოქმედების ღილაკი, რათა მოძებნოთ ის, რაც თქვენს ეკრანზეა"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"ეს პროდუქტი ძიებისთვის იყენებს თქვენი ეკრანის არჩეულ ნაწილს. მოქმედებს Google-ის <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>კონფიდენციალურობის დებულება<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> და <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>მომსახურებს პირობები<xliff:g id="END_TOS_LINK"></a></xliff:g>."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"დახურვა"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"მზადაა"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"მთავარი"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ზემოთ/მარცხნივ გადატანა"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ქვემოთ/მარჯვნივ გადატანა"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{#-ით მეტი აპის ჩენება}other{#-ით მეტი აპის ჩვენება.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# დესკტოპის აპის ჩვენება.}other{# დესკტოპის აპის ჩვენება.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> და <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"მიმდინარეობს აპის დესკტოპზე დამატება"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"გაუქმება"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ბუშტი"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"გადავსება"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: <xliff:g id="APP_NAME">%2$s</xliff:g>-იდან"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> და <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> სხვა"</string>
</resources>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index ad944c5..d83e2d3 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Бекіту"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Еркін форма"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Бөлісу"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
<string name="action_split" msgid="2098009717623550676">"Бөлу"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Қолданбаларды жұптауды сақтау"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Экранды бөлу режимін пайдалану үшін басқа қолданбаны түртіңіз."</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Экранды бөлу үшін басқа қолданбаны таңдаңыз."</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Бас тарту"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Тапсырмалар жолағында мүмкіндік көп"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Тапсырмалар жолағын әрдайым көрсету"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Экранның төменгі жағында тапсырмалар жолағы әрдайым көрсетілуі үшін, бөлгішті басып тұрыңыз."</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Экраннан іздеу үшін әрекет пернесін басып тұрыңыз"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Бұл өнім іздеу үшін экранның таңдалған бөлігін пайдаланады. Google <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Құпиялық саясаты<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> мен <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Қызмет көрсету шарттары<xliff:g id="END_TOS_LINK"></a></xliff:g> қолданылады."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Жабу"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Дайын"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Негізгі экран"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Жоғары/солға жылжыту"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Төмен/оңға жылжыту"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Тағы # қолданбаны көрсету.}other{Тағы # қолданбаны көрсету.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Компьютерге арналған # қолданбаны көрсету}other{Компьютерге арналған # қолданбаны көрсету}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> және <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Жұмыс үстеліне қолданба қосу"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Бас тарту"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Қалқыма терезе"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Қосымша мәзір"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> ұсынатын <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> және тағы <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 3a56606..b6f2532 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"ខ្ទាស់"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"មុខងារទម្រង់សេរី"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ដែសថប"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"ចែករំលែក"</string>
<string name="action_screenshot" msgid="8171125848358142917">"រូបថតអេក្រង់"</string>
<string name="action_split" msgid="2098009717623550676">"បំបែក"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"រក្សាទុកគូកម្មវិធី"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"ចុចកម្មវិធីផ្សេងទៀត ដើម្បីប្រើមុខងារបំបែកអេក្រង់"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"ជ្រើសរើសកម្មវិធីផ្សេងទៀត ដើម្បីប្រើមុខងារបំបែកអេក្រង់"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"បោះបង់"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"ធ្វើបានកាន់តែច្រើនដោយប្រើរបារកិច្ចការ"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"បង្ហាញរបារកិច្ចការជានិច្ច"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"ដើម្បីបង្ហាញរបារកិច្ចការនៅផ្នែកខាងក្រោមនៃអេក្រង់របស់អ្នកជានិច្ច សូមចុចបន្ទាត់ខណ្ឌចែកឱ្យជាប់"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"ចុចគ្រាប់ចុចសកម្មភាពឱ្យជាប់ ដើម្បីស្វែងរកអ្វីដែលមាននៅលើអេក្រង់របស់អ្នក"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"ផលិតផលនេះប្រើប្រាស់ផ្នែកដែលបានជ្រើសរើសនៃអេក្រង់របស់អ្នក ដើម្បីស្វែងរក។ <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>គោលការណ៍ឯកជនភាព<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> និង<xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>លក្ខខណ្ឌប្រើប្រាស់<xliff:g id="END_TOS_LINK"></a></xliff:g>របស់ Google ត្រូវបានអនុវត្ត។"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"បិទ"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"រួចរាល់"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"ទំព័រដើម"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ផ្លាស់ទីទៅខាងលើ/ឆ្វេង"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ផ្លាស់ទីទៅខាងក្រោម/ស្ដាំ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{បង្ហាញកម្មវិធី # ទៀត។}other{បង្ហាញកម្មវិធី # ទៀត។}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{បង្ហាញកម្មវិធីកុំព្យូទ័រ #។}other{បង្ហាញកម្មវិធីកុំព្យូទ័រ #។}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> និង <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"កំពុងបញ្ចូលកម្មវិធីទៅកុំព្យូទ័រ"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"បោះបង់"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ផ្ទាំងសារ"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ម៉ឺនុយបន្ថែម"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ពី <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> និង <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> នាក់ទៀត"</string>
</resources>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 6759404..74c7750 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"ಪಿನ್ ಮಾಡಿ"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ಮುಕ್ತಸ್ವರೂಪ"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ಡೆಸ್ಕ್ಟಾಪ್"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"ಹಂಚಿಕೊಳ್ಳಿ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ಸ್ಕ್ರೀನ್ಶಾಟ್"</string>
<string name="action_split" msgid="2098009717623550676">"ವಿಭಜಿಸಿ"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ಆ್ಯಪ್ ಪೇರ್ ಸೇವ್ ಮಾಡಿ"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಬಳಸಲು ಬೇರೆ ಆ್ಯಪ್ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಬಳಸಲು ಇನ್ನೊಂದು ಆ್ಯಪ್ ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"ರದ್ದುಮಾಡಿ"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"ಟಾಸ್ಕ್ಬಾರ್ ಮೂಲಕ ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಿ"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"ಯಾವಾಗಲೂ ಟಾಸ್ಕ್ಬಾರ್ ಅನ್ನು ತೋರಿಸಿ"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"ಯಾವಾಗಲೂ ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ನ ಕೆಳಭಾಗದಲ್ಲಿ ಟಾಸ್ಕ್ ಬಾರ್ ಅನ್ನು ತೋರಿಸಲು, ಡಿವೈಡರ್ ಅನ್ನು ಸ್ಪರ್ಶಿಸಿ ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳಿ"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ನ ಮೇಲೆ ಏನಿದೆ ಎಂಬುದನ್ನು ಹುಡುಕಲು ಆ್ಯಕ್ಷನ್ ಕೀಯನ್ನು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಒತ್ತಿ ಹಿಡಿದುಕೊಳ್ಳಿ"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"ಈ ಉತ್ಪನ್ನವು ಹುಡುಕಲು ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ನ ಆಯ್ದ ಭಾಗವನ್ನು ಬಳಸುತ್ತದೆ. Google ನ <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>ಗೌಪ್ಯತೆ ನೀತಿ<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> ಮತ್ತು <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>ಸೇವಾ ನಿಯಮಗಳು<xliff:g id="END_TOS_LINK"></a></xliff:g> ಅನ್ವಯಿಸುತ್ತವೆ."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"ಮುಚ್ಚಿರಿ"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"ಆಯಿತು"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"ಮುಖಪುಟ"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ಮೇಲಿನ/ಎಡಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ಕೆಳಗಿನ/ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ಇನ್ನೂ # ಆ್ಯಪ್ ಅನ್ನು ತೋರಿಸಿ.}one{ಇನ್ನೂ # ಆ್ಯಪ್ಗಳನ್ನು ತೋರಿಸಿ.}other{ಇನ್ನೂ # ಆ್ಯಪ್ಗಳನ್ನು ತೋರಿಸಿ.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ಡೆಸ್ಕ್ಟಾಪ್ ಆ್ಯಪ್ ತೋರಿಸಿ.}one{# ಡೆಸ್ಕ್ಟಾಪ್ ಆ್ಯಪ್ಗಳನ್ನು ತೋರಿಸಿ.}other{# ಡೆಸ್ಕ್ಟಾಪ್ ಆ್ಯಪ್ಗಳನ್ನು ತೋರಿಸಿ.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ಮತ್ತು <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ಡೆಸ್ಕ್ಟಾಪ್ಗೆ ಆ್ಯಪ್ ಅನ್ನು ಸೇರಿಸಲಾಗುತ್ತಿದೆ"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ರದ್ದುಮಾಡಿ"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ಬಬಲ್"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ಓವರ್ಫ್ಲೋ"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> ನಿಂದ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ಮತ್ತು ಇನ್ನೂ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index b83bfa2..c27b7f8 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"고정"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"자유 형식"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"데스크톱"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"공유"</string>
<string name="action_screenshot" msgid="8171125848358142917">"스크린샷"</string>
<string name="action_split" msgid="2098009717623550676">"분할"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"앱 페어링 저장"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"다른 앱을 탭하여 화면 분할 사용"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"화면 분할을 사용하려면 다른 앱을 선택하세요."</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"취소"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"태스크 바 최대한 활용하기"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"태스크 바 항상 표시"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"화면 하단에 태스크 바를 항상 표시하려면 구분선을 길게 터치하세요."</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"화면 내용을 검색하려면 작업 키 길게 터치"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"이 제품은 사용자가 화면에서 선택한 부분을 사용하여 검색하며, Google <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>개인정보처리방침<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> 및 <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>서비스 약관<xliff:g id="END_TOS_LINK"></a></xliff:g>이 적용됩니다."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"닫기"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"완료"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"홈"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"상단/왼쪽으로 이동"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"하단/오른쪽으로 이동"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{앱 #개 더 표시}other{앱 #개 더 표시}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{데스크톱 앱 #개를 표시합니다.}other{데스크톱 앱 #개를 표시합니다.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> 및 <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"데스크톱에 앱 추가하기"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"취소"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"풍선"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"더보기"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>의 <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> 외 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>개"</string>
</resources>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 9920b02..f0d2af8 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Кадап коюу"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Эркин форма режими"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Бөлүшүү"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
<string name="action_split" msgid="2098009717623550676">"Бөлүү"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Колдонмолорду сактап коюу"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Экранды бөлүү үчүн башка колдонмону таптап коюңуз"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Экранды бөлүү үчүн башка колдонмону тандаңыз"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Жокко чыгаруу"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Тапшырмалар тактасы менен көбүрөөк иш бүтүрөсүз"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Тапшырмалар панелин ар дайым көрсөтүү"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Тапшырмалар панелин экрандын ылдый жагында ар дайым көрсөтүү үчүн бөлгүчтү коё бербей басыңыз"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Экрандагы нерсени издөө үчүн аракет баскычын коё бербей кармап туруңуз"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Бул кызмат издөө үчүн экранда тандалган бөлүктү колдонот. Google\'дун <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Купуялык эрежелери<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> жана <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Тейлөө шарттары<xliff:g id="END_TOS_LINK"></a></xliff:g> колдонулат."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Жабуу"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Бүттү"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Башкы бет"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Жогорку/сол бурчка жылдыруу"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Төмөнкү/оң бурчка жылдыруу"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Дагы # колдонмону көрсөтүү.}other{Дагы # колдонмону көрсөтүү.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# иш такта колдонмосун көрсөтүү.}other{# иш такта колдонмосун көрсөтүү.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> жана <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Колдонмону иш тактага кошуу"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Жокко чыгаруу"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Көбүкчө"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Кошумча меню"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> колдонмосунан <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> жана дагы <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index 2b17b93..e862b9e 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -78,7 +78,8 @@
<!-- Taskbar 3 button spacing -->
<dimen name="taskbar_button_margin_split">88dp</dimen>
<dimen name="taskbar_button_margin_6_5">219.6dp</dimen>
- <dimen name="taskbar_contextual_button_margin">48dp</dimen>
+ <dimen name="taskbar_contextual_button_suw_margin">48dp</dimen>
+ <dimen name="taskbar_contextual_button_suw_height">48dp</dimen>
<dimen name="taskbar_suw_frame">96dp</dimen>
<dimen name="taskbar_suw_insets">24dp</dimen>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index fa72bd1..f54c712 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"ປັກໝຸດ"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ຮູບແບບອິດສະຫລະ"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ເດັສທັອບ"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"ແບ່ງປັນ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ຮູບໜ້າຈໍ"</string>
<string name="action_split" msgid="2098009717623550676">"ແບ່ງ"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ບັນທຶກຈັບຄູ່ແອັບ"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"ແຕະແອັບອື່ນເພື່ອໃຊ້ໜ້າຈໍແຍກ"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"ເລືອກແອັບອື່ນເພື່ອໃຊ້ການແບ່ງໜ້າຈໍ"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"ຍົກເລີກ"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"ເຮັດສິ່ງຕ່າງໆໄດ້ຫຼາຍຂຶ້ນດ້ວຍແຖບໜ້າວຽກ"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"ສະແດງແຖບໜ້າວຽກສະເໝີ"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"ເພື່ອໃຫ້ແຖບໜ້າວຽກສະແດງຢູ່ລຸ່ມໜ້າຈໍຂອງທ່ານຢູ່ສະເໝີ, ໃຫ້ແຕະຕົວແບ່ງຄ້າງໄວ້"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"ແຕະປຸ່ມຄຳສັ່ງຄ້າງໄວ້ເພື່ອຊອກຫາສິ່ງທີ່ຢູ່ເທິງໜ້າຈໍຂອງທ່ານ"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"ສິນຄ້ານີ້ໃຊ້ສ່ວນທີ່ເລືອກຂອງໜ້າຈໍຂອງທ່ານເພື່ອຊອກຫາ. ເປັນໄປຕາມ <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>ນະໂຍບາຍຄວາມເປັນສ່ວນຕົວ<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> ແລະ <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>ຂໍ້ກຳນົດບໍລິການ<xliff:g id="END_TOS_LINK"></a></xliff:g> ຂອງ Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"ປິດ"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"ແລ້ວໆ"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"ໜ້າຫຼັກ"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ຍ້າຍໄປຊ້າຍ/ເທິງ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ຍ້າຍໄປຂວາ/ລຸ່ມ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ສະແດງອີກ # ແອັບ.}other{ສະແດງອີກ # ແອັບ.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{ສະແດງແອັບເດັສທັອບ # ລາຍການ.}other{ສະແດງແອັບເດັສທັອບ # ລາຍການ.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ແລະ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ການເພີ່ມແອັບໄປໃສ່ເດັສທັອບ"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ຍົກເລີກ"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ຟອງ"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ລາຍການເພີ່ມເຕີມ"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ຈາກ <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ແລະ ອີກ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ລາຍການ"</string>
</resources>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 6e71234..554745e 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Prisegti"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Laisva forma"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Stalinis kompiuteris"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Išvalyti viską"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Bendrinti"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Ekrano kopija"</string>
<string name="action_split" msgid="2098009717623550676">"Išskaidymo režimas"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Išsaug. progr. porą"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Išskaidyto ekrano režimas palietus kitą programą"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Išskaidyto ekrano režimą naudokite kita programa"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Atšaukti"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Atlikite daugiau naudodami Užduočių juostą"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Visada rodyti užduočių juostą"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Jei norite, kad užduočių juosta visada būtų rodoma ekrano apačioje, palieskite ir palaikykite daliklį"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Ekrane rodomo turinio paieška palietus ir laikant veiksmų klavišą"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Šis produktas paieškai naudoja pasirinktą ekrano dalį. Taikomos „Google“ <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>privatumo politikos<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> ir <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>paslaugų teikimo sąlygos<xliff:g id="END_TOS_LINK"></a></xliff:g>."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Uždaryti"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Atlikta"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Pagrindinis"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Perkelti aukštyn, kairėn"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Perkelti žemyn, dešinėn"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Rodyti dar # programą.}one{Rodyti dar # programą.}few{Rodyti dar # programas.}many{Rodyti dar # programos.}other{Rodyti dar # programų.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Rodyti # darbalaukio programą.}one{Rodyti # darbalaukio programą.}few{Rodyti # darbalaukio programas.}many{Rodyti # darbalaukio programos.}other{Rodyti # darbalaukio programų.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"„<xliff:g id="APP_NAME_1">%1$s</xliff:g>“ ir „<xliff:g id="APP_NAME_2">%2$s</xliff:g>“"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Pridedama programa prie darbalaukio"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Atšaukti"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbulas"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Perpildymas"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"„<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>“ iš „<xliff:g id="APP_NAME">%2$s</xliff:g>“"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"„<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>“ ir dar <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index 8fd7b12..a6a0dab 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Piespraust"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Brīva forma"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Dators"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Notīrīt visu"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Kopīgot"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Veikt ekrānuzņēmumu"</string>
<string name="action_split" msgid="2098009717623550676">"Sadalīt"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Saglabāt pāri"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Lai sadalītu ekrānu, pieskarieties citai lietotnei"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Izvēlieties citu lietotni, lai sadalītu ekrānu"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Atcelt"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Plašākas iespējas, izmantojot uzdevumu joslu"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Vienmēr rādīt uzdevumu joslu"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Lai uzdevumu joslu rādītu apakšdaļā, pieskarieties atdalītājam un turiet"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Pieskarieties darbību taustiņam un turiet to, lai meklētu ekrānā redzamo saturu"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Šajā produktā atlasītā ekrāna daļa tiek izmantota meklēšanai. Ir spēkā Google <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>konfidencialitātes politika<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> un <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>pakalpojumu sniegšanas noteikumi<xliff:g id="END_TOS_LINK"></a></xliff:g>."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Aizvērt"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Gatavs"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Sākums"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Pārvietot uz augšējo/kreiso stūri"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pārvietot uz apakšējo/labo stūri"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Rādīt vēl # lietotni}zero{Rādīt vēl # lietotnes}one{Rādīt vēl # lietotni}other{Rādīt vēl # lietotnes}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Rādīt # datora lietotni.}zero{Rādīt # datora lietotnes.}one{Rādīt # datora lietotni.}other{Rādīt # datora lietotnes.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"“<xliff:g id="APP_NAME_1">%1$s</xliff:g>” un “<xliff:g id="APP_NAME_2">%2$s</xliff:g>”"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Notiek lietotnes pievienošana datoram"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Atcelt"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbulis"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Pārpilde"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> no lietotnes <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> un vēl <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index a36f7b5..8fff5b6 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Закачи"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Работна површина"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Сподели"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Слика од екранот"</string>
<string name="action_split" msgid="2098009717623550676">"Раздели"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Зачувај го паров"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Допрете друга аплик. за да користите поделен екран"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Изберете друга апликација за да користите поделен екран"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Откажи"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Правете сешто со „Лентата со задачи“"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Како секогаш да се прикажува „Лентата со задачи“"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Допрете и задржете го разделникот за да може „Лентата со задачи“ секогаш да се прикажува на дното на екранот"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Допрете и задржете го копчето за дејство за да пребарувате на екранот"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Производов го користи избраниот дел од екранот за пребарување. Важат <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Политиката за приватност<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> и <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Условите за користење<xliff:g id="END_TOS_LINK"></a></xliff:g> на Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Затвори"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Готово"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Дома"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Премести горе лево"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Премести долу десно"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Прикажи уште # апликација.}one{Прикажи уште # апликација.}other{Прикажи уште # апликации.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Прикажи # апликација за компјутер.}one{Прикажи # апликација за компјутер.}other{Прикажи # апликации за компјутер.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Додавање на апликацијата во „Работна површина“"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Откажи"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Балонче"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Проширено балонче"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> од <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> и уште <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index 3e6d075..85b093d 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"പിൻ ചെയ്യുക"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ഫ്രീഫോം"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ഡെസ്ക്ടോപ്പ്"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"പങ്കിടുക"</string>
<string name="action_screenshot" msgid="8171125848358142917">"സ്ക്രീൻഷോട്ട്"</string>
<string name="action_split" msgid="2098009717623550676">"വിഭജിക്കുക"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ആപ്പ് ജോടി സംരക്ഷിക്കൂ"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"സ്പ്ലിറ്റ് സ്ക്രീനിന് മറ്റൊരു ആപ്പിൽ ടാപ്പ് ചെയ്യൂ"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"സ്ക്രീൻ വിഭജന മോഡ് ഉപയോഗിക്കാൻ മറ്റൊരു ആപ്പ് തിരഞ്ഞെടുക്കൂ"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"റദ്ദാക്കുക"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"ടാസ്ക്ബാർ ഉപയോഗിച്ച് കൂടുതൽ ചെയ്യുക"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"എല്ലായ്പ്പോഴും ടാസ്ക്ബാർ കാണിക്കുക"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"ടാസ്ക്ബാർ എല്ലായ്പ്പോഴും നിങ്ങളുടെ സ്ക്രീനിന്റെ ചുവടെ കാണിക്കുന്നതിന് ഡിവൈഡറിൽ സ്പർശിച്ച് പിടിക്കുക"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"നിങ്ങളുടെ സ്ക്രീനിൽ എന്താണ് ഉള്ളതെന്ന് തിരയാൻ ആക്ഷൻ കീ സ്പർശിച്ച് പിടിക്കുക"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"തിരയുന്നതിന് ഈ ഉൽപ്പന്നം സ്ക്രീനിലെ തിരഞ്ഞെടുത്ത ഭാഗം ഉപയോഗിക്കുന്നു. Google-ന്റെ <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>സ്വകാര്യതാ നയവും<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>സേവന നിബന്ധനകളും<xliff:g id="END_TOS_LINK"></a></xliff:g> ബാധകമാണ്."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"അടയ്ക്കുക"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"പൂർത്തിയായി"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"ഹോം"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"മുകളിലേക്കോ ഇടത്തേക്കോ നീക്കുക"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"താഴേക്കോ വലത്തേക്കോ നീക്കുക"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# ആപ്പ് കൂടി കാണിക്കുക.}other{# ആപ്പുകൾ കൂടി കാണിക്കുക.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ഡെസ്ക്ടോപ്പ് ആപ്പ് കാണിക്കുക.}other{# ഡെസ്ക്ടോപ്പ് ആപ്പുകൾ കാണിക്കുക.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g>, <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ആപ്പ് ഡെസ്ക്ടോപ്പിലേക്ക് ചേർക്കുന്നു"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"റദ്ദാക്കുക"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ബബിൾ"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ഓവർഫ്ലോ"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> എന്നതിൽ നിന്നുള്ള <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> എന്നതും മറ്റ് <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> എണ്ണവും"</string>
</resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 71e7fb5..fe2e4a4 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Бэхлэх"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Чөлөөтэй хувьсах"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Хуваалцах"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Дэлгэцийн агшин дарах"</string>
<string name="action_split" msgid="2098009717623550676">"Хуваах"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Апп хослуулалт хадгал"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Дэлгэцийг хуваахыг ашиглахын тулд өөр аппыг товш"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Дэлгэц хуваахыг ашиглахын тулд өөр апп сонгоно уу"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Цуцлах"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Ажлын хэсгийн тусламжтай илүү ихийг хийгээрэй"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Ажлын хэсгийг үргэлж харуулах"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Дэлгэцийнхээ доод талд Ажлын хэсгийг үргэлж харуулахын тулд хуваагч дээр хүрээд удаан дарна уу"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Дэлгэц дээрээ байгаа зүйлийг хайхын тулд тусгай товчлуурыг удаан дарна уу"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Энэ бүтээгдэхүүн хайхын тулд таны дэлгэцийн сонгосон хэсгийг ашигладаг. Google-н <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>нууцлалын бодлого<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> болон <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>үйлчилгээний нөхцөл<xliff:g id="END_TOS_LINK"></a></xliff:g> хэрэгжинэ."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Хаах"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Дууссан"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Гэр"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Зүүн дээд хэсэг рүү зөөх"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Баруун доод хэсэг рүү зөөх"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Өөр # аппыг харуулна уу.}other{Өөр # аппыг харуулна уу.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Компьютерын # аппыг харуулна уу.}other{Компьютерын # аппыг харуулна уу.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> болон <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Компьютерт апп нэмж байна"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Цуцлах"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Бөмбөлөг"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Илүү хэсэг"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>-с ирсэн <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> болон бусад <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index eea6f85..b053a21 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"पिन करा"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रीफॉर्म"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
<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>
@@ -94,7 +95,8 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"सिस्टीम नेव्हिगेशन सेटिंग्ज"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"शेअर करा"</string>
<string name="action_screenshot" msgid="8171125848358142917">"स्क्रीनशॉट"</string>
- <string name="action_split" msgid="2098009717623550676">"स्प्लिट"</string>
+ <string name="action_split" msgid="2098009717623550676">"स्प्लिट करा"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ॲपची जोडी सेव्ह करा"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"स्प्लिट स्क्रीन वापरण्यासाठी दुसऱ्या ॲपवर टॅप करा"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"स्प्लिट स्क्रीन वापरण्यासाठी दुसरे ॲप निवडा"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"रद्द करा"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"टास्कबार चा पुरेपूर वापर करा"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"टास्कबार नेहमी दाखवा"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"टास्कबार नेहमी तुमच्या स्क्रीनच्या तळाशी दाखवण्यासाठी, विभाजकाला स्पर्श करून धरून ठेवा"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"तुमच्या स्क्रीनवरील गोष्टी शोधण्यासाठी अॅक्शन की स्पर्श करून धरून ठेवा"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"हे उत्पादन शोधण्याच्या हेतूसाठी तुमच्या स्क्रीनचा निवडक भाग वापरते. Google चे <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>गोपनीयता धोरण<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> आणि <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>सेवा अटी<xliff:g id="END_TOS_LINK"></a></xliff:g> लागू होतात."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"बंद करा"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"पूर्ण झाले"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"होम"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"सर्वात वरती/डावीकडे हलवा"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"तळाशी/उजवीकडे हलवा"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{आणखी # अॅप दाखवा.}other{आणखी # अॅप्स दाखवा.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# डेस्कटॉप अॅप दाखवा.}other{# डेस्कटॉप अॅप्स दाखवा.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> आणि <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"डेस्कटॉपवर ॲप जोडत आहे"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"रद्द करा"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"बबल"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ओव्हरफ्लो"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> वरील <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> आणि आणखी <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index 09b15b5..c0219e0 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Semat"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Bentuk bebas"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Tiada item terbaharu"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tetapan penggunaan apl"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Kosongkan semua"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Kongsi"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Tangkapan skrin"</string>
<string name="action_split" msgid="2098009717623550676">"Pisah"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Simpan gandingan apl"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Ketik apl lain untuk menggunakan skrin pisah"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Pilih apl lain untuk menggunakan skrin pisah"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Batal"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Lakukan lebih banyak perkara dengan Bar Tugas"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Sentiasa paparkan Bar Tugas"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Untuk sentiasa memaparkan Bar Tugas pada bahagian bawah skrin, sentuh & tahan pembahagi"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Sentuh & tahan kekunci tindakan untuk mencari kandungan yang dipaparkan pada skrin anda"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Produk ini menggunakan bahagian yang dipilih pada skrin anda untuk membuat carian. <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Dasar Privasi<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> dan <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Syarat Perkhidmatan<xliff:g id="END_TOS_LINK"></a></xliff:g> Google digunakan."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Tutup"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Selesai"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Laman Utama"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Alihkan ke atas/kiri"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Alihkan ke bawah/kanan"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Tunjukkan # lagi apl.}other{Tunjukkan # lagi apl.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Tunjukkan # apl desktop.}other{Tunjukkan # apl desktop.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> dan <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Menambahkan apl pada Desktop"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Batal"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Gelembung"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Limpahan"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> daripada <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> dan <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> lagi"</string>
</resources>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index e8f2e1f..7c7ff82 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"ပင်ထိုးရန်"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"အလွတ်ပုံစံ"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ဒက်စ်တော့"</string>
<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>
@@ -94,7 +95,8 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"စနစ် လမ်းညွှန် ဆက်တင်များ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"မျှဝေရန်"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ဖန်သားပြင်ဓာတ်ပုံ"</string>
- <string name="action_split" msgid="2098009717623550676">"ခွဲထုတ်ရန်"</string>
+ <string name="action_split" msgid="2098009717623550676">"ခွဲရန်"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"အက်ပ်အတွဲ သိမ်းရန်"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"မျက်နှာပြင် ခွဲ၍ပြသရန် အက်ပ်နောက်တစ်ခုကို တို့ပါ"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းသုံးရန် နောက်အက်ပ်တစ်ခုရွေးပါ"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"မလုပ်တော့"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Taskbar ဖြင့် ပိုမိုလုပ်ဆောင်နိုင်ခြင်း"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Taskbar ကို အမြဲပြပါ"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Taskbar ကို စခရင်အောက်ခြေတွင် အမြဲပြရန် ခွဲခြားမျဉ်းကို တို့ထိ၍ ဖိထားပါ"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"သင့်စခရင်ပေါ်ရှိအရာကို ရှာရန် လုပ်ဆောင်ချက်ကီးကို ထိ၍ဖိထားပါ"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"ဤကုန်ပစ္စည်းသည် သင့်စခရင်၌ ရွေးထားသောအပိုင်းကိုသုံး၍ ရှာဖွေသည်။ Google ၏ <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>ကိုယ်ရေးအချက်အလက်လုံခြုံမှုဆိုင်ရာ မူဝါဒ<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> နှင့် <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>ဝန်ဆောင်မှုစည်းမျဉ်းများ<xliff:g id="END_TOS_LINK"></a></xliff:g> အကျုံးဝင်သည်။"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"ပိတ်ရန်"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"ပြီးပြီ"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"ပင်မစာမျက်နှာ"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"အပေါ်/ဘယ်ဘက်သို့ ရွှေ့ရန်"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"အောက်ခြေ/ညာဘက်သို့ ရွှေ့ရန်"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{နောက်ထပ်အက်ပ် # ခု ပြပါ။}other{နောက်ထပ်အက်ပ် # ခု ပြပါ။}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{ဒက်စတော့ အက်ပ် # ခု ပြပါ။}other{ဒက်စတော့ အက်ပ် # ခု ပြပါ။}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> နှင့် <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"‘ဒက်စ်တော့’ တွင် အက်ပ်ကို ထည့်ခြင်း"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"မလုပ်တော့"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ပူဖောင်းကွက်"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"မီနူးအပို"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> မှ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> နှင့် နောက်ထပ် <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ခု"</string>
</resources>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index ffbb73c..6aa755a 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fest"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivebord"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ingen nylige elementer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Innstillinger for appbruk"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Fjern alt"</string>
@@ -93,8 +94,9 @@
<string name="default_device_name" msgid="6660656727127422487">"enheten"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Innstillinger for systemnavigasjon"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Del"</string>
- <string name="action_screenshot" msgid="8171125848358142917">"Skjermdump"</string>
+ <string name="action_screenshot" msgid="8171125848358142917">"Skjermbilde"</string>
<string name="action_split" msgid="2098009717623550676">"Del opp"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Lagre apptilkobling"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Trykk på en annen app for å bruke delt skjerm"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Velg en annen app for å bruke delt skjerm"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Avbryt"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Gjør mer med oppgavelinjen"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Vis alltid oppgavelinjen"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"For å alltid vise oppgavelinjen nederst på skjermen, trykk og hold på skillelinjen"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Trykk og hold på handlingstasten for å søke etter det som er på skjermen"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Dette produktet bruker den merkede delen av skjermen til å søke. Googles <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>personvernregler<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> og <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>vilkår for bruk<xliff:g id="END_TOS_LINK"></a></xliff:g> gjelder."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Lukk"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Ferdig"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Hjem"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flytt til øverst/venstre"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytt til nederst/høyre"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Vis # app til.}other{Vis # apper til.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Vis # datamaskinprogram.}other{Vis # datamaskinprogrammer.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> og <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Legg til apper på datamaskin"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Avbryt"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Boble"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflyt"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> fra <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> og <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> andre"</string>
</resources>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 8e2e44f..d49fd2d 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"पिन गर्नुहोस्"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रिफर्म"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटप"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"सेयर गर्नुहोस्"</string>
<string name="action_screenshot" msgid="8171125848358142917">"स्क्रिनसट"</string>
<string name="action_split" msgid="2098009717623550676">"स्प्लिट गर्नुहोस्"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"एपको पेयर सेभ गर्नुहोस्"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"स्प्लिटस्क्रिन प्रयोग गर्न अर्को एपमा ट्याप गर्नु…"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"स्प्लिट स्क्रिन प्रयोग गर्न अर्को एप रोज्नुहोस्"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"रद्द गर्नुहोस्"</b></string>
@@ -113,8 +115,10 @@
<string name="taskbar_edu_suggestions" msgid="8215044496435527982">"आफ्नो रुटिनका आधारमा एपसम्बन्धी सुझावहरू प्राप्त गर्नुहोस्"</string>
<string name="taskbar_edu_pinning" msgid="6708550858580071558">"टास्कबार पिन गर्न डिभाइडरमा केही बेरसम्म थिच्नुहोस्"</string>
<string name="taskbar_edu_features" msgid="3320337287472848162">"टास्कबार प्रयोग गरेर अझ धेरै कार्य गर्नुहोस्"</string>
- <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"टास्कबार सधैँ देखाइयोस्"</string>
+ <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"टास्कबार सधैँ देखाउनुहोस्"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"आफ्नो स्क्रिनको पुछारमा टास्कबार सधैँ देखाइराख्न डिभाइडर टच एन्ड होल्ड गर्नुहोस्"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"आफ्नो स्क्रिनमा भएका कुराहरू खोज्न एक्सन कीमा टच एन्ड होल्ड गर्नुहोस्"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"यो उत्पादनले तपाईंले चयन गर्नुभएको स्क्रिनको भाग प्रयोग गरेर खोजी गर्छ। Google को <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>गोपनीयता नीति<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> र <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>सेवाका सर्तहरू<xliff:g id="END_TOS_LINK"></a></xliff:g> लागू हुन्छन्।"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"बन्द गर्नुहोस्"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"सम्पन्न भयो"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"होम"</string>
@@ -128,13 +132,16 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"टास्कबार देखाइएको छ"</string>
<string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"टास्कबार लुकाइएको छ"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"नेभिगेसन बार"</string>
- <string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार सधैँ देखाइयोस्"</string>
+ <string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार सधैँ देखाउनुहोस्"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"नेभिगेसन मोड बदल्नुहोस्"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"टास्कबार डिभाइडर"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"सिरान/बायाँतिर सार्नुहोस्"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"फेद/दायाँतिर सार्नुहोस्"</string>
- <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{थप # एप देखाइयोस्।}other{थप # वटा एप देखाइयोस्।}}"</string>
+ <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{थप # एप देखाउनुहोस्।}other{थप # वटा एप देखाउनुहोस्।}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# डेस्कटप एप देखाउनुहोस्।}other{# वटा डेस्कटप एप देखाउनुहोस्।}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> र <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"डेस्कटपमा एप हालिँदै छ"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"रद्द गर्नुहोस्"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"बबल"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ओभरफ्लो"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> मा देखाइएका <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> र थप <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-night/styles.xml b/quickstep/res/values-night/styles.xml
index ed0bd5b..401351f 100644
--- a/quickstep/res/values-night/styles.xml
+++ b/quickstep/res/values-night/styles.xml
@@ -25,6 +25,21 @@
<item name="android:windowBackground">@android:color/transparent</item>
</style>
+ <style name="TextAppearance.GestureTutorial.MainTitle.Home"
+ parent="TextAppearance.GestureTutorial.MainTitle">
+ <item name="android:textColor">?attr/onSurfaceHome</item>
+ </style>
+
+ <style name="TextAppearance.GestureTutorial.MainTitle.Back"
+ parent="TextAppearance.GestureTutorial.MainTitle">
+ <item name="android:textColor">?attr/onSurfaceBack</item>
+ </style>
+
+ <style name="TextAppearance.GestureTutorial.MainTitle.Overview"
+ parent="TextAppearance.GestureTutorial.MainTitle">
+ <item name="android:textColor">?attr/onSurfaceOverview</item>
+ </style>
+
<style name="TextAppearance.GestureTutorial.MenuButton.Home"
parent="TextAppearance.GestureTutorial.MenuButton">
<item name="android:textColor">?attr/onSurfaceHome</item>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index 12cadbb..ca44a69 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Vastzetten"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vrije vorm"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Geen recente items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Instellingen voor app-gebruik"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Alles wissen"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Delen"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Splitsen"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"App-paar opslaan"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tik op nog een app om je scherm te splitsen"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Kies een andere app om gesplitst scherm te gebruiken"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Annuleren"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Doe meer met de taakbalk"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"De taakbalk altijd tonen"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Houd de scheidingslijn ingedrukt als je de taakbalk altijd onderaan je scherm wilt tonen"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Houd je vinger op de actietoets om te zoeken naar wat er op je scherm staat"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Dit product gebruikt het geselecteerde gedeelte van je scherm om te zoeken. Het <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Privacybeleid<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> en de <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Servicevoorwaarden<xliff:g id="END_TOS_LINK"></a></xliff:g> van Google zijn van toepassing."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Sluiten"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Klaar"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Home"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Naar boven/links verplaatsen"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Naar beneden/rechts verplaatsen"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Nog # app tonen.}other{Nog # apps tonen.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# desktop-app tonen.}other{# desktop-apps tonen.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> en <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"App toevoegen aan desktop"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annuleren"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubbel"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overloop"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> van <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> en nog <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 9c32921..bf0bdc8 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"ପିନ୍"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ଫ୍ରିଫର୍ମ"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ଡେସ୍କଟପ"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"ସେୟାର୍ କରନ୍ତୁ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ସ୍କ୍ରିନସଟ୍"</string>
<string name="action_split" msgid="2098009717623550676">"ସ୍ପ୍ଲିଟ୍"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ଆପ ପେୟାର ସେଭ କରନ୍ତୁ"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"ସ୍ପ୍ଲିଟସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପରେ ଟାପ କର"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପ ବାଛନ୍ତୁ"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"ବାତିଲ କରନ୍ତୁ"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"ଟାସ୍କବାର ମାଧ୍ୟମରେ ଆହୁରି ଅନେକ କିଛି କରନ୍ତୁ"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"ସର୍ବଦା ଟାସ୍କବାର ଦେଖାନ୍ତୁ"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"ଆପଣଙ୍କ ସ୍କ୍ରିନର ନିମ୍ନରେ ସର୍ବଦା ଟାସ୍କବାର ଦେଖାଇବା ପାଇଁ ଡିଭାଇଡରକୁ ସ୍ପର୍ଶ କରି ଧରି ରଖନ୍ତୁ"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"ଆପଣଙ୍କ ସ୍କ୍ରିନରେ କଣ ଅଛି ତାହା ସର୍ଚ୍ଚ କରିବା ପାଇଁ ଆକ୍ସନ କୀ\'କୁ ସ୍ପର୍ଶ କରି ଧରି ରଖନ୍ତୁ"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"ସର୍ଚ୍ଚ କରିବା ପାଇଁ ଏହି ପ୍ରଡକ୍ଟ ଆପଣଙ୍କ ସ୍କ୍ରିନର ଚୟନିତ ଅଂଶକୁ ବ୍ୟବହାର କରେ। Googleର <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>ଗୋପନୀୟତା ନୀତି<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> ଏବଂ <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>ସେବାର ସର୍ତ୍ତାବଳୀ<xliff:g id="END_TOS_LINK"></a></xliff:g> ଲାଗୁ ହୁଏ।"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"ହୋଇଗଲା"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"ହୋମ"</string>
@@ -123,7 +127,7 @@
<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_quick_settings" msgid="227662894293189391">"କ୍ୱିକ ସେଟିଂସ"</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_hidden_title" msgid="9154903639589659284">"ଟାସ୍କବାର ଲୁଚାଯାଇଛି"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ଶୀର୍ଷ/ବାମକୁ ମୁଭ କରନ୍ତୁ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ନିମ୍ନ/ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ଅଧିକ #ଟି ଆପ ଦେଖାନ୍ତୁ।}other{ଅଧିକ #ଟି ଆପ୍ସ ଦେଖାନ୍ତୁ।}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ଡେସ୍କଟପ ଆପ ଦେଖାନ୍ତୁ।}other{# ଡେସ୍କଟପ ଆପ୍ସ ଦେଖାନ୍ତୁ।}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ଏବଂ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ଡେସ୍କଟପରେ ଆପ ଯୋଗ କରାଯାଉଛି"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ବାତିଲ କରନ୍ତୁ"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ବବଲ"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ଓଭରଫ୍ଲୋ"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>ରୁ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ଏବଂ ଅଧିକ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index e467303..fc60396 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"ਪਿੰਨ ਕਰੋ"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ਫ੍ਰੀਫਾਰਮ"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ਡੈਸਕਟਾਪ"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"ਸਾਂਝਾ ਕਰੋ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string>
<string name="action_split" msgid="2098009717623550676">"ਸਪਲਿਟ"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ਐਪ ਜੋੜਾਬੱਧ ਰੱਖਿਅਤ ਕਰੋ"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਨੂੰ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ \'ਤੇ ਟੈਪ ਕਰੋ"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਨੂੰ ਚੁਣੋ"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"ਰੱਦ ਕਰੋ"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"ਟਾਸਕਬਾਰ ਦਾ ਹੋਰ ਲਾਹਾ ਲਓ"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"ਹਮੇਸ਼ਾਂ ਟਾਸਕਬਾਰ ਦਿਖਾਉਣਾ"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਹਮੇਸ਼ਾਂ ਟਾਸਕਬਾਰ ਦਿਖਾਉਣ ਲਈ, ਵਿਭਾਜਕ ਨੂੰ ਸਪਰਸ਼ ਕਰ ਕੇ ਰੱਖੋ"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਮੌਜੂਦ ਸਮੱਗਰੀ ਨੂੰ ਖੋਜਣ ਲਈ, ਕਾਰਵਾਈ ਕੁੰਜੀ ਨੂੰ ਸਪਰਸ਼ ਕਰ ਕੇ ਰੱਖੋ"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"ਇਹ ਉਤਪਾਦ ਖੋਜ ਕਰਨ ਲਈ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ ਦੇ ਚੁਣੇ ਹੋਏ ਹਿੱਸੇ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। Google ਦੀ <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>ਪਰਦੇਦਾਰੀ ਨੀਤੀ<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> ਅਤੇ <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>ਸੇਵਾ ਦੇ ਨਿਯਮ<xliff:g id="END_TOS_LINK"></a></xliff:g> ਲਾਗੂ ਹੁੰਦੇ ਹਨ।"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"ਬੰਦ ਕਰੋ"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"ਸਮਝ ਲਿਆ"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"ਘਰ"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ਸਿਖਰਲੇ/ਖੱਬੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ਹੇਠਾਂ/ਸੱਜੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# ਹੋਰ ਐਪ ਦਿਖਾਓ।}one{# ਹੋਰ ਐਪ ਦਿਖਾਓ।}other{# ਹੋਰ ਐਪਾਂ ਦਿਖਾਓ।}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ਡੈਸਕਟਾਪ ਐਪ ਦਿਖਾਓ।}one{# ਡੈਸਕਟਾਪ ਐਪ ਦਿਖਾਓ।}other{# ਡੈਸਕਟਾਪ ਐਪਾਂ ਦਿਖਾਓ।}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ਅਤੇ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ਐਪ ਨੂੰ ਡੈਸਕਟਾਪ \'ਤੇ ਸ਼ਾਮਲ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ਰੱਦ ਕਰੋ"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ਬਬਲ"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ਓਵਰਫ਼ਲੋ"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> ਤੋਂ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ਅਤੇ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ਹੋਰ"</string>
</resources>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 439ee35..13c8c1c 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Przypnij"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Tryb dowolny"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Pulpit"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Wyczyść wszystko"</string>
@@ -89,12 +90,13 @@
<string name="allset_title" msgid="5021126669778966707">"Wszystko gotowe"</string>
<string name="allset_hint" msgid="459504134589971527">"Aby przejść na stronę główną, przesuń w górę"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Kliknij przycisk ekranu głównego, aby otworzyć ekran główny"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"Teraz możesz zacząć używać urządzenia <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
- <string name="default_device_name" msgid="6660656727127422487">"urządzenie"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> jest gotowe – możesz zacząć z niego korzystać"</string>
+ <string name="default_device_name" msgid="6660656727127422487">"Urządzenie"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Ustawienia nawigacji w systemie"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Udostępnij"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Zrzut ekranu"</string>
<string name="action_split" msgid="2098009717623550676">"Podziel"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Zapisz parę"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Aby podzielić ekran, kliknij drugą aplikację"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Aby podzielić ekran, wybierz drugą aplikację"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Anuluj"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Wykorzystaj potencjał paska aplikacji"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Zawsze wyświetlaj pasek aplikacji"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Aby zawsze wyświetlać pasek aplikacji u dołu ekranu, naciśnij i przytrzymaj linię dzielenia ekranu"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Aby rozpocząć wyszukiwanie na podstawie zawartości ekranu, naciśnij i przytrzymaj klawisz działania"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Usługa przy wyszukiwaniu używa zaznaczonego fragmentu ekranu. Obowiązują zapisy <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Polityki prywatności<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> i <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Warunków korzystania z usług<xliff:g id="END_TOS_LINK"></a></xliff:g> Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Zamknij"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Gotowe"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Ekran główny"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Przesuń w górny lewy róg"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Przesuń w dolny prawy róg"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Pokaż jeszcze # aplikację.}few{Pokaż jeszcze # aplikacje.}many{Pokaż jeszcze # aplikacji.}other{Pokaż jeszcze # aplikacji.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Pokaż # aplikację komputerową.}few{Pokaż # aplikacje komputerowe.}many{Pokaż # aplikacji komputerowych.}other{Pokaż # aplikacji komputerowej.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Dodaję aplikację do komputera"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Anuluj"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Dymek"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Rozwijany"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> z aplikacji <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> i jeszcze <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index bacdcf0..e4d07bd 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Computador"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Limpar tudo"</string>
@@ -56,7 +57,7 @@
<string name="back_gesture_intro_title" msgid="19551256430224428">"Deslize rapidamente com o dedo para retroceder"</string>
<string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Para voltar ao último ecrã, deslize rapidamente do limite esquerdo ou direito até ao centro do ecrã."</string>
<string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Para voltar ao último ecrã, deslize rapidamente com 2 dedos a partir da extremidade esquerda ou direita até ao centro do ecrã."</string>
- <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Voltar"</string>
+ <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Retroceder"</string>
<string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Deslize rapidamente a partir da extremidade esquerda ou direita para o meio do ecrã"</string>
<string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Deslize rapidamente com o dedo a partir do limite inferior do ecrã"</string>
<string name="home_gesture_feedback_overview_detected" msgid="5177627157303895077">"Não faça uma pausa antes de soltar"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Partilhar"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Fazer captura de ecrã"</string>
<string name="action_split" msgid="2098009717623550676">"Dividir"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Guardar par de apps"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toque noutra app para usar o ecrã dividido"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Escolha outra app para usar o ecrã dividido"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Cancelar"</b></string>
@@ -111,10 +113,12 @@
<string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Arraste uma app para o lado para usar 2 apps em simultâneo"</string>
<string name="taskbar_edu_stashing" msgid="5645461372669217294">"Deslize lentamente para cima para ver a Barra de tarefas"</string>
<string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Receba sugestões de apps baseadas na sua rotina"</string>
- <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Mantenha o divisor premido para fixar a Barra de tarefas"</string>
+ <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Mantenha o divisor pressionado para fixar a Barra de tarefas"</string>
<string name="taskbar_edu_features" msgid="3320337287472848162">"Faça mais com a Barra de tarefas"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostre sempre a Barra de tarefas"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Para mostrar sempre a Barra de tarefas no fundo do ecrã, toque sem soltar no divisor"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Toque sem soltar na tecla de ação para pesquisar o que está no ecrã"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este produto usa a parte selecionada do ecrã para pesquisar. Aplicam-se a <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de Privacidade<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> e os <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Termos de Utilização<xliff:g id="END_TOS_LINK"></a></xliff:g> da Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Fechar"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Concluir"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Início"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover para a parte superior esquerda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover para a part superior direita"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar mais # app.}other{Mostrar mais # apps.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostrar # app para computador.}other{Mostrar # apps para computador.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"A adicionar a app ao computador"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancelar"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Balão"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Menu adicional"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> da app <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> e mais <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> pessoas"</string>
</resources>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 2fdf05d..ef406db 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Modo área de trabalho"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Remover tudo"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Compartilhar"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Capturar tela"</string>
<string name="action_split" msgid="2098009717623550676">"Dividir"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Salvar par de apps"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toque em outro app para usar a tela dividida"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Escolha outro app para usar na tela dividida"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Cancelar"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Aproveite ainda mais a Barra de tarefas"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Sempre mostrar a Barra de tarefas"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Toque e pressione o divisor para sempre mostrar a Barra de tarefas na parte de baixo da tela"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Toque na tecla de ação e pressione para pesquisar o que está na tela"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"O produto usa a parte selecionada da tela para pesquisar. O uso desses dados está sujeito à <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de Privacidade<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> e aos <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Termos de Serviço<xliff:g id="END_TOS_LINK"></a></xliff:g> do Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Fechar"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Concluído"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Início"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover para cima/para a esquerda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover para baixo/para a direita"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar mais # app.}one{Mostrar mais # app.}other{Mostrar mais # apps.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostrar # app para computador.}one{Mostrar # app para computador.}other{Mostrar # apps para computador.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adicionando app ao computador"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancelar"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Balão"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Balão flutuante"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> do app <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> e mais <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index 9363392..225bccf 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixează"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formă liberă"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Șterge tot"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Distribuie"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Captură de ecran"</string>
<string name="action_split" msgid="2098009717623550676">"Împărțit"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Salvează perechea de aplicații"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Atinge altă aplicație pentru ecranul împărțit"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Alege altă aplicație pentru a folosi ecranul împărțit"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Anulează"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Fă mai multe din Bara de activități"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Afișează întotdeauna Bara de activități"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Pentru a afișa mereu Bara de activități în partea de jos a ecranului, atinge lung separatorul"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Atinge lung tasta de acțiuni ca să cauți pe ecran"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Acest produs folosește partea selectată a ecranului pentru a căuta. Se aplică <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Politica de confidențialitate<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> și <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Termenii și condițiile<xliff:g id="END_TOS_LINK"></a></xliff:g> Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Închide"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Gata"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Ecran de pornire"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mută în stânga sus"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mută în dreapta jos"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afișează încă # aplicație}few{Afișează încă # aplicații}other{Afișează încă # de aplicații}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Afișează # aplicație pentru computer.}few{Afișează # aplicații pentru computer.}other{Afișează # de aplicații pentru computer.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> și <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Se adaugă aplicația pe computer"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Anulează"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Balon"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Suplimentar"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de la <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> și încă <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 02ebb29..da49ad3 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Закрепить"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Произвольная форма"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Включить режим для ПК"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Поделиться"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
<string name="action_split" msgid="2098009717623550676">"Разделить"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Сохранить приложения"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Для разделения экрана выберите другое приложение."</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Чтобы использовать разделенный экран, выберите другое приложение."</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Отмена"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Используйте все возможности панели задач"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Закрепите панель задач внизу экрана"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Для этого нажмите на разделитель и удерживайте его."</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Чтобы найти информацию об объекте на экране, нажмите и удерживайте клавишу действия"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Этот продукт использует выделенную часть экрана для поиска. При этом действуют <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Политика конфиденциальности<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> и <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Условия использования<xliff:g id="END_TOS_LINK"></a></xliff:g> Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Закрыть"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Готово"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Главный экран"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Переместить вверх или влево"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Переместить вниз или вправо"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Показать ещё # приложение}one{Показать ещё # приложение}few{Показать ещё # приложения}many{Показать ещё # приложений}other{Показать ещё # приложения}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Показать # компьютерное приложение.}one{Показать # компьютерное приложение.}few{Показать # компьютерных приложения.}many{Показать # компьютерных приложений.}other{Показать # компьютерного приложения.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Добавление приложения на компьютер"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Отмена"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Всплывающая подсказка"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Дополнительное меню"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"\"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\" из приложения \"<xliff:g id="APP_NAME">%2$s</xliff:g>\""</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> и ещё <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index cef1091..9cbe837 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"අමුණන්න"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ඩෙස්ක්ටොපය"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"බෙදා ගන්න"</string>
<string name="action_screenshot" msgid="8171125848358142917">"තිර රුව"</string>
<string name="action_split" msgid="2098009717623550676">"බෙදන්න"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"යෙදුම් යුගල සුරකින්න"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"බෙදුම් තිරය භාවිතා කිරීමට තවත් යෙදුමක් තට්ටු කරන්න"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"බෙදුම් තිරය භාවිත කිරීමට වෙනත් යෙදුමක් තෝරා ගන්න"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"අවලංගු කරන්න"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"කාර්ය තීරුව සමග තවත් කරන්න"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"සෑම විටම කාර්ය තීරුව පෙන්වන්න"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"සෑම විටම ඔබේ තිරයේ පතුලේ ඇති කාර්ය තීරුව පෙන්වීමට, බෙදුම්කරු ස්පර්ශ කර අල්ලාගෙන සිටින්න"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"ඔබේ තිරයෙහි ඇති දේ සෙවීමට ක්රියා යතුර ස්පර්ශ කර සිටින්න"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"මෙම නිෂ්පාදනය සෙවීමට ඔබේ තිරයෙහි තෝරන ලද කොටස භාවිතා කරයි. Google හි <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>පෞද්ගලිකත්ව ප්රතිපත්තිය<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> සහ <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>සේවා නියමයන්<xliff:g id="END_TOS_LINK"></a></xliff:g> අදාළ වේ."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"වසන්න"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"නිමයි"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"මුල් පිටුව"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ඉහළ/වම වෙත ගෙන යන්න"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"පහළ/දකුණ වෙත ගෙන යන්න"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{තවත් # යෙදුමක් පෙන්වන්න.}one{තවත් යෙදුම් #ක් පෙන්වන්න.}other{තවත් යෙදුම් #ක් පෙන්වන්න.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ඩෙස්ක්ටොප් යෙදුමක් පෙන්වන්න.}one{ඩෙස්ක්ටොප් යෙදුම් # ක් පෙන්වන්න.}other{ඩෙස්ක්ටොප් යෙදුම් # ක් පෙන්වන්න.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> සහ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ඩෙස්ක්ටොප් වෙත යෙදුම එක් කිරීම"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"අවලංගු කරන්න"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"බුබුළු"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"පිටාර යාම"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> සිට <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> හා තව <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>ක්"</string>
</resources>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 49acd72..3eca787 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pripnúť"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Voľný režim"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Počítač"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Vymazať všetko"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Zdieľať"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snímka obrazovky"</string>
<string name="action_split" msgid="2098009717623550676">"Rozdeliť"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Uložiť pár aplikácií"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Obrazovku rozdelíte klepnutím na inú aplikáciu"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Na použitie rozdelenej obrazovky vyberte ďalšiu aplikáciu"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Zrušiť"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Panel aplikácií vám ponúka ďalšie možnosti"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Vždy zobrazovať panel aplikácií"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Ak chcete, aby sa panel aplikácií vždy zobrazoval v dolnej časti obrazovky, pridržte rozdeľovač"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Ak chcete vyhľadávať, čo je na obrazovke, pridržte akčný kláves"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Táto služba používa na účely vyhľadávania vybranú časť obrazovky. Uplatňujú sa <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>pravidlá ochrany súkromia<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> a <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>zmluvné podmienky<xliff:g id="END_TOS_LINK"></a></xliff:g> spoločnosti Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Zavrieť"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Hotovo"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Plocha"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Presunúť hore alebo doľava"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Presunúť dole alebo doprava"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Zobraziť # ďalšiu aplikáciu.}few{Zobraziť # ďalšie aplikácie.}many{Show # more apps.}other{Zobraziť # ďalších aplikácií.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Zobraziť # aplikáciu pre počítač.}few{Zobraziť # aplikácie pre počítač.}many{Show # desktop apps.}other{Zobraziť # aplikácií pre počítač.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> a <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Pridanie aplikácie na plochu"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Zrušiť"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bublina"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Rozbaľovacia ponuka"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> z aplikácie <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> a ešte <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index 9f7a928..52faeb7 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pripni"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Prosta oblika"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Namizni računalnik"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ni nedavnih elementov"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavitve uporabe aplikacij"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Počisti vse"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Deli"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Posnetek zaslona"</string>
<string name="action_split" msgid="2098009717623550676">"Razdeli"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Shrani par aplikacij"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Za razdeljeni zaslon se dotaknite še 1 aplikacije"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Izberite drugo aplikacijo za uporabo razdeljenega zaslona."</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Prekliči"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Naredite več z opravilno vrstico"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Stalni prikaz opravilne vrstice"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Če želite, da je opravilna vrstica vedno prikazana na dnu zaslona, pridržite razdelilno črto."</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Za iskanje po zaslonu se dotaknite in pridržite tipko za dejanja"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Ta izdelek za iskanje uporablja izbrani del zaslona. Veljajo Googlov <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>pravilnik o zasebnosti<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> in <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>pogoji storitve<xliff:g id="END_TOS_LINK"></a></xliff:g>."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Zapri"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Končano"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Začetni zaslon"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premakni na vrh/levo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premakni na dno/desno"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Pokaži še # aplikacijo.}one{Pokaži še # aplikacijo.}two{Pokaži še # aplikaciji.}few{Pokaži še # aplikacije.}other{Pokaži še # aplikacij.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Prikaz # aplikacije za namizni računalnik.}one{Prikaz # aplikacije za namizni računalnik.}two{Prikaz # aplikacij za namizni računalnik.}few{Prikaz # aplikacij za namizni računalnik.}other{Prikaz # aplikacij za namizni računalnik.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> in <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Dodajanje aplikacije na namizje"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Prekliči"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Oblaček"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Oblaček z dodatnimi elementi"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> in še <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index bf44975..cdb9cf9 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Gozhdo"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formë e lirë"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopi"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Pastroji të gjitha"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Ndaj"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Pamja e ekranit"</string>
<string name="action_split" msgid="2098009717623550676">"Ndaj"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Ruaj çiftin e aplikacioneve"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Trokit një apl. tjetër; përdor ekranin e ndarë"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Zgjidh një aplikacion tjetër për të përdorur ekranin e ndarë"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Anulo"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Bëj më shumë me \"Shiritin e detyrave\""</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Shfaq gjithmonë \"Shiritin e detyrave\""</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Prek e mbaj ndarësin dhe shfaq gjithmonë \"Shiritin e detyrave\" në fund të ekranit"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Prek dhe mbaj shtypur tastin e veprimit për të kërkuar për gjërat në ekran"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Ky produkt përdor pjesën e zgjedhur të ekranit tënd për të kërkuar. Zbatohen <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Politika e privatësisë<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> dhe <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Kushtet e shërbimit<xliff:g id="END_TOS_LINK"></a></xliff:g> të Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Mbyll"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"U krye"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Faqja kryesore"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Lëviz në krye/majtas"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Lëviz në fund/djathtas"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Shfaq # aplikacion tjetër.}other{Shfaq # aplikacione të tjera.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Shfaq # aplikacion për desktop.}other{Shfaq # aplikacione për desktop.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> dhe <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Shtimi i aplikacionit te desktopi"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Anulo"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Flluska"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Tejkalimi"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"\"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\" nga <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"\"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>\" dhe <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> të tjera"</string>
</resources>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index a7bce7b..7456a36 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Закачи"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Слободни облик"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Рачунар"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Дели"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Снимак екрана"</string>
<string name="action_split" msgid="2098009717623550676">"Подели"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Сачувај пар апликација"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Додирните другу апликацију за подељени екран"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Одаберите другу апликацију да бисте користили подељени екран"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Откажи"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Урадите више помоћу траке задатака"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Увек приказуј траку задатака"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Да би трака задатака увек била приказана у дну екрана, додирните и задржите разделник"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Додирните и задржите тастер радњи да бисте претражили оно што је на екрану"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Овај производ користи изабрани део екрана за претрагу. Примењују се Google <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>политика приватности<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> и <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>услови коришћења услуге<xliff:g id="END_TOS_LINK"></a></xliff:g>."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Затвори"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Готово"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Почетна"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Премести горе лево"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Премести доле десно"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Прикажи још # апликацију.}one{Прикажи још # апликацију.}few{Прикажи још # апликације.}other{Прикажи још # апликација.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Прикажи # апликацију за рачунаре.}one{Прикажи # апликацију за рачунаре.}few{Прикажи # апликације за рачунаре.}other{Прикажи # апликација за рачунаре.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Додаје се апликација на радну поврршину"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Откажи"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Облачић"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Преклопни"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> и још <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index 58e5fc4..f369dae 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fäst"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Dator"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Rensa alla"</string>
@@ -94,7 +95,8 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Systemnavigeringsinställningar"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Dela"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skärmbild"</string>
- <string name="action_split" msgid="2098009717623550676">"Delat"</string>
+ <string name="action_split" msgid="2098009717623550676">"Delad skärm"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Spara app-par"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tryck på en annan app för att använda delad skärm"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Välj en annan app för att använda delad skärm"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Avbryt"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Gör mer med aktivitetsfältet"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Visa alltid aktivitetsfältet"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Tryck länge på avgränsaren för att alltid visa aktivitetsfältet längst ned på skärmen"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Tryck länge på åtgärdstangenten för att söka efter det som finns på skärmen"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Den här produkten använder den valda delen av skärmen för att söka. Googles <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>integritetspolicy<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> och <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>användarvillkor<xliff:g id="END_TOS_LINK"></a></xliff:g> gäller."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Stäng"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Klar"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Startsida"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flytta högst upp/till vänster"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytta längst ned/till höger"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Visa # app till.}other{Visa # appar till.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Visa # datorapp.}other{Visa # datorappar.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> och <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Lägger till appen på skrivbordet"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Avbryt"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubbla"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Fler alternativ"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> från <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> och <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> till"</string>
</resources>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index c5a9df0..3d8277b 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Bandika"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Muundo huru"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Kompyuta ya mezani"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Ondoa zote"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Shiriki"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Picha ya skrini"</string>
<string name="action_split" msgid="2098009717623550676">"Iliyogawanywa"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Hifadhi jozi ya programu"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Gusa programu nyingine ili utumie kipengele cha kugawa skrini"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Chagua programu nyingine ili utumie hali ya kugawa skrini"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Ghairi"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Kamilisha mengi kwa kutumia Upauzana huu"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Onyesha Upauzana kila wakati"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Ili uonyeshe Upauzana kila wakati chini ya skrini yako, gusa na ushikilie kitenganishi"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Gusa na ushikilie kitufe cha vitendo ili utafute kilicho kwenye skrini yako"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Bidhaa hii hutumia sehemu uliyochagua kwenye skrini yako kutafuta. <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Sera ya Faragha<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> na <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Sheria na Masharti<xliff:g id="END_TOS_LINK"></a></xliff:g> ya Google yatatumika."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Funga"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Imemaliza"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Mwanzo"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Sogeza juu/kushoto"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sogeza chini/kulia"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Onyesha programu # zaidi.}other{Onyesha programu # zaidi.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Onyesha programu # ya kompyuta ya mezani.}other{Onyesha programu # za kompyuta ya mezani.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> na <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Kuweka programu kwenye Eneo-kazi"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Ghairi"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Kiputo"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Kiputo cha vipengee vya ziada"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> kutoka <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> na vingine <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index f9528b3..e24d8fe 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -45,4 +45,8 @@
<dimen name="allset_page_allset_text_size">38sp</dimen>
<dimen name="allset_page_swipe_up_text_size">15sp</dimen>
+ <!-- Splitscreen -->
+ <!-- Max width of the split instructions view -->
+ <dimen name="split_instructions_view_max_width">300dp</dimen>
+
</resources>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index a15824c..47d8055 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"பின் செய்தல்"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"குறிப்பிட்ட வடிவமில்லாத பயன்முறை"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"டெஸ்க்டாப்"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"பகிர்"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ஸ்கிரீன்ஷாட்"</string>
<string name="action_split" msgid="2098009717623550676">"பிரி"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ஆப்ஸ் ஜோடியைச் சேமி"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"திரைப் பிரிப்பைப் பயன்படுத்த வேறு ஆப்ஸைத் தட்டவும்"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"திரைப் பிரிப்பைப் பயன்படுத்த வேறு ஆப்ஸைத் தேர்வுசெய்யுங்கள்"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"ரத்துசெய்"</b></string>
@@ -115,10 +117,12 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"செயல் பட்டி மூலம் மேலும் பலவற்றைச் செய்யுங்கள்"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"செயல் பட்டியை எப்போதும் காட்டுதல்"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"திரையின் கீழ்ப்பகுதியில் செயல் பட்டியை எப்போதும் காட்டுவதற்கு, பிரிப்பானைத் தொட்டுப் பிடித்திருக்கவும்"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"உங்கள் திரையில் உள்ளவற்றைத் தேடுவதற்கு ஆக்ஷன் பட்டனைத் தொட்டுப் பிடியுங்கள்"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"தேடுவதற்கு உங்கள் திரையின் தேர்ந்தெடுக்கப்பட்ட பகுதியை இந்தத் தயாரிப்பு பயன்படுத்துகிறது. Googleளின் <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>தனியுரிமைக் கொள்கையும்<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>சேவை விதிமுறைகளும்<xliff:g id="END_TOS_LINK"></a></xliff:g> பொருந்தும்."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"மூடுக"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"முடிந்தது"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"முகப்பு"</string>
- <string name="taskbar_button_a11y" msgid="5241161324875094465">"அணுகல்தன்மை"</string>
+ <string name="taskbar_button_a11y" msgid="5241161324875094465">"மாற்றுத்திறன் வசதி"</string>
<string name="taskbar_button_back" msgid="8558862226461164514">"பின்செல்லும்"</string>
<string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME சுவிட்ச்சர்"</string>
<string name="taskbar_button_recents" msgid="7273376136216613134">"சமீபத்தியவை"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"மேலே/இடதுபுறம் நகர்த்தும்"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"கீழே/வலதுபுறம் நகர்த்தும்"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{மேலும் # ஆப்ஸைக் காட்டு.}other{மேலும் # ஆப்ஸைக் காட்டு.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# டெஸ்க்டாப் ஆப்ஸைக் காட்டு.}other{# டெஸ்க்டாப் ஆப்ஸைக் காட்டு.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> மற்றும் <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ஆப்ஸை டெஸ்க்டாப்பில் சேர்க்கிறது"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ரத்துசெய்"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"குமிழ்"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"கூடுதல் விருப்பங்களைக் காட்டும்"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> வழங்கும் <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> மற்றும் <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 1c42203..a4e1cbf 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"పిన్ చేయండి"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"సంప్రదాయేతర"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"డెస్క్టాప్"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"షేర్ చేయండి"</string>
<string name="action_screenshot" msgid="8171125848358142917">"స్క్రీన్షాట్"</string>
<string name="action_split" msgid="2098009717623550676">"స్ప్లిట్ చేయండి"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"యాప్ పెయిర్ను సేవ్ చేయండి"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"స్ప్లిట్ స్క్రీన్ కోసం మరొక యాప్ను ట్యాప్ చేయండి"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"స్ప్లిట్ స్క్రీన్ను ఉపయోగించడానికి మరొక యాప్ ఎంచుకోండి"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"రద్దు చేయండి"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"టాస్క్బార్తో మరిన్ని చేయండి"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"టాస్క్బార్ను నిరంతరం చూపండి"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"మీ స్క్రీన్ దిగువున టాస్క్బార్ను నిరంతరం చూపడానికి, డివైడర్ను తాకి, నొక్కి ఉంచండి"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"మీ స్క్రీన్లో ఏం ఉందో సెర్చ్ చేయడానికి యాక్షన్ కీని తాకి ఉంచండి"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"ఈ ప్రోడక్ట్, సెర్చ్ చేయడానికి మీ స్క్రీన్లో ఎంచుకున్న భాగాన్ని ఉపయోగిస్తుంది. Google <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>గోప్యతా పాలసీ<xliff:g id="END_PRIVACY_LINK"></a></xliff:g>, <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>సర్వీస్ నియమాలు<xliff:g id="END_TOS_LINK"></a></xliff:g> వర్తిస్తాయి."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"మూసివేయండి"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"పూర్తయింది"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"మొదటి ట్యాబ్"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ఎగువ/ఎడమ వైపునకు తరలించండి"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"దిగువ/కుడి వైపునకు తరలించండి"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{మరో # యాప్ను చూడండి.}other{మరో # యాప్లను చూడండి.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# డెస్క్టాప్ యాప్ను చూపండి.}other{# డెస్క్టాప్ యాప్లను చూపండి.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g>, <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"డెస్క్టాప్నకు యాప్ను జోడిస్తోంది"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"రద్దు చేయండి"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"బబుల్"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ఓవర్ఫ్లో"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> నుండి <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>, మరో <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index 3b246d6..1bbb137 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"ปักหมุด"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"รูปแบบอิสระ"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"เดสก์ท็อป"</string>
<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>
@@ -91,10 +92,11 @@
<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>
- <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"การตั้งค่าการนำทางของระบบ"</annotation></string>
+ <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"การตั้งค่าการไปยังส่วนต่างๆ ของระบบ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"แชร์"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ภาพหน้าจอ"</string>
<string name="action_split" msgid="2098009717623550676">"แยก"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"บันทึกคู่แอป"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"แตะแอปอื่นเพื่อใช้การแยกหน้าจอ"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"เลือกแอปอื่นเพื่อใช้การแยกหน้าจอ"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"ยกเลิก"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"ทำสิ่งต่างๆ ได้มากขึ้นด้วยแถบงาน"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"แสดงแถบงานเสมอ"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"หากต้องการให้แถบงานแสดงที่ด้านล่างหน้าจออยู่เสมอ ให้แตะตัวแบ่งค้างไว้"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"แตะปุ่มดำเนินการค้างไว้เพื่อค้นหาสิ่งที่อยู่บนหน้าจอ"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"ผลิตภัณฑ์นี้ใช้ส่วนที่เลือกของหน้าจอเพื่อค้นหา เป็นไปตาม<xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>นโยบายความเป็นส่วนตัว<xliff:g id="END_PRIVACY_LINK"></a></xliff:g>และ<xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>ข้อกําหนดในการให้บริการ<xliff:g id="END_TOS_LINK"></a></xliff:g>ของ Google"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"ปิด"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"เสร็จ"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"หน้าแรก"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ย้ายไปที่ด้านบนหรือด้านซ้าย"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ย้ายไปที่ด้านล่างหรือด้านขวา"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{แสดงเพิ่มเติมอีก # แอป}other{แสดงเพิ่มเติมอีก # แอป}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{แสดงแอปบนเดสก์ท็อป # รายการ}other{แสดงแอปบนเดสก์ท็อป # รายการ}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> และ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"การเพิ่มแอปไปยังเดสก์ท็อป"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ยกเลิก"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"บับเบิล"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"การดำเนินการเพิ่มเติม"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> จาก <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> และอีก <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> รายการ"</string>
</resources>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 08e030d..978a5a3 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"I-pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"I-clear lahat"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Ibahagi"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"I-save ang app pair"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Mag-tap ng ibang app para gamitin ang split screen"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Pumili ng ibang app para gamitin ang split screen"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Kanselahin"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Mas maraming magawa gamit ang Taskbar"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Palaging ipakita ang Taskbar"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Para palaging ipakita ang Taskbar sa ibaba ng iyong screen, pindutin nang matagal ang divider"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Pindutin nang matagal ang action key para hanapin kung ano ang nasa iyong screen"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Ginagamit ng produktong ito ang piniling bahagi ng iyong screen para maghanap. Nalalapat ang <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Patakaran sa Privacy<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> at <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Mga Tuntunin ng Serbisyo<xliff:g id="END_TOS_LINK"></a></xliff:g> ng Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Isara"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Tapos na"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Home"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Ilipat sa itaas/kaliwa"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Ilipat sa ibaba/kanan"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Magpakita ng # pang app.}one{Magpakita ng # pang app.}other{Magpakita ng # pang app.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Ipakita ang # desktop app.}one{Ipakita ang # desktop app.}other{Ipakita ang # na desktop app.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> at <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Idinaragdag ang app sa Desktop"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Kanselahin"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> mula sa <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> at <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> pa"</string>
</resources>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index cedfa2f..0cc5d7f 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Sabitle"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Serbest çalışma"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Yeni öğe yok"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Uygulama kullanım ayarları"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Tümünü temizle"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Paylaş"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Ekran görüntüsü"</string>
<string name="action_split" msgid="2098009717623550676">"Böl"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Uygulama çiftini kaydet"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Bölünmüş ekran için başka bir uygulamaya dokunun"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Bölünmüş ekran kullanmak için başka bir uygulama seçin"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"İptal"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Görev çubuğuyla daha fazla şey yapın"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Görev çubuğunu sabitleyin"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Ayırıcıya dokunup basılı tuttuğunuzda görev çubuğu ekranın alt kısmına sabitlenir"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Ekranınızda bulunan içerikleri aramak için eylem tuşuna dokunup basılı tutun"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Bu ürün, ekranınızın seçilen bölümünü kullanarak arama yapar. Google\'ın <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Gizlilik Politikası<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> ve <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Hizmet Şartları<xliff:g id="END_TOS_LINK"></a></xliff:g> geçerlidir."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Kapat"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Bitti"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Ana ekran"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Sol üste taşı"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sağ alta taşı"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# uygulama daha göster.}other{# uygulama daha göster}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# masaüstü uygulamasını göster.}other{# masaüstü uygulamasını göster.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ve <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Uygulama Masaüstü\'ne ekleniyor"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"İptal"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Balon"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Taşma"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> uygulamasından <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ve <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> tane daha"</string>
</resources>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 71257c8..d9274e5 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Закріпити"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Довільна форма"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Робочий стіл"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Поділитися"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Знімок екрана"</string>
<string name="action_split" msgid="2098009717623550676">"Розділити"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Зберегти пару"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Щоб розділити екран, виберіть ще один додаток."</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Щоб розділити екран, виберіть ще один додаток."</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Скасувати"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Більше можливостей завдяки панелі завдань"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Завжди показувати панель завдань"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Щоб завжди показувати панель завдань унизу екрана, натисніть і втримуйте роздільник"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Для пошуку інформації, що відображається на екрані, натисніть і втримуйте клавішу дії"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Цей продукт використовує для пошуку вибрану частину екрана. Застосовуються <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Політика конфіденційності<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> та <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Загальні положення й умови<xliff:g id="END_TOS_LINK"></a></xliff:g> Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Закрити"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Готово"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Головний екран"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Перемістити вгору або вліво"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Перемістити вниз або вправо"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Показати ще # додаток.}one{Показати ще # додаток.}few{Показати ще # додатки.}many{Показати ще # додатків.}other{Показати ще # додатка.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Показати # комп’ютерну програму.}one{Показати # комп’ютерну програму.}few{Показати # комп’ютерні програми.}many{Показати # комп’ютерних програм.}other{Показати # комп’ютерної програми.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> та <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Встановлення додатка на комп’ютер"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Скасувати"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Повідомлення"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Додаткове повідомлення"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> з додатка <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> і ще <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 9beaf07..e125248 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"پن کریں"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"فری فارم"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"ڈیسک ٹاپ"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"اشتراک کریں"</string>
<string name="action_screenshot" msgid="8171125848358142917">"اسکرین شاٹ"</string>
<string name="action_split" msgid="2098009717623550676">"اسپلٹ"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"ایپس کے جوڑے کو محفوظ کریں"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"اسپلٹ اسکرین کا استعمال کرنے کیلئے دوسری ایپ پر تھپتھپائیں"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"اسپلٹ اسکرین کے استعمال کیلئے دوسری ایپ منتخب کریں"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"منسوخ کریں"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"ٹاسک بار سے بہت کچھ کریں"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"ہمیشہ ٹاسک بار دکھائیں"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"ٹاسک بار کو ہمیشہ اپنی اسکرین کے نیچے دکھانے کے لیے، ڈیوائیڈر کو ٹچ کریں اور دبائے رکھیں"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"آپ کی اسکرین پر جو کچھ ہے اسے تلاش کرنے کے لیے ایکشن کلید کو ٹچ کریں اور دبائے رکھیں"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"یہ پروڈکٹ تلاش کرنے کے لیے آپ کی اسکرین کا منتخب حصہ استعمال کرتا ہے۔ Google کی <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>رازداری کی پالیسی<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> اور <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>سروس کی شرائط<xliff:g id="END_TOS_LINK"></a></xliff:g> لاگو ہوتی ہیں۔"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"بند کریں"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"ہو گیا"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"ہوم"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"اوپر/بائیں طرف منتقل کریں"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"نیچے/دائیں طرف منتقل کریں"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# مزید ایپ دکھائیں۔}other{# مزید ایپس دکھائیں۔}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ڈیسک ٹاپ ایپ دکھائیں۔}other{# ڈیسک ٹاپ ایپس دکھائیں۔}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> اور <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"ڈیسک ٹاپ پر ایپ شامل کرنا"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"منسوخ کریں"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ببل"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"اوورفلو"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> سے <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> اور <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> مزید"</string>
</resources>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index d2f434f..3f4f981 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Qadash"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Erkin shakl"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Hammasini tozalash"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Ulashish"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skrinshot"</string>
<string name="action_split" msgid="2098009717623550676">"Ajratish"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Ilova juftini saqlash"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Ekranni ikkiga ajratish uchun boshqa ilovani bosing"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Ekranni ikkiga ajratish uchun boshqa ilovani tanlang"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Bekor qilish"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Vazifalar panelidan maksimal darajada foydalaning"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Vazifalar paneli doim chiqarilsin"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Vazifalar panelini ekranning pastki qismida doim chiqib turishi uchun ajratkichni bosib turing"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Ekrandagi element haqida maʼlumotni topish uchun amal tugmasini bosib turing"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Bu mahsulot qidirish uchun ekranning tanlangan qismidan foydalanadi. Google <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>maxfiylik siyosati<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> va <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>xizmat shartlari<xliff:g id="END_TOS_LINK"></a></xliff:g> tatbiq qilinadi."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Yopish"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Tayyor"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Bosh ekran"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Yuqoriga yoki chapga oʻtkazish"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pastga yoki oʻngga oʻtkazish"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Yana # ta ilovani chiqarish}other{Yana # ta ilovani chiqarish}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ta desktop ilovani chiqarish.}other{# ta desktop ilovani chiqarish.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> va <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Ilova kompyuterga qoʻshilmoqda"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Bekor qilish"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Pufak"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Kengaytirish"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> (<xliff:g id="APP_NAME">%2$s</xliff:g>)"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> va yana <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> kishi"</string>
</resources>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index 626c569..9bc526f 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Ghim"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Dạng tự do"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Máy tính"</string>
<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>
<string name="recents_clear_all" msgid="5328176793634888831">"Xóa tất cả"</string>
@@ -91,10 +92,11 @@
<string name="allset_button_hint" msgid="2395219947744706291">"Nhấn vào nút màn hình chính để chuyển đến màn hình chính"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"Bạn có thể bắt đầu sử dụng <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="default_device_name" msgid="6660656727127422487">"thiết bị"</string>
- <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Chế độ cài đặt di chuyển trên hệ thống"</annotation></string>
+ <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Cài đặt cách thao tác trên hệ thống"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Chia sẻ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Chụp ảnh màn hình"</string>
<string name="action_split" msgid="2098009717623550676">"Chia đôi màn hình"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Lưu cặp ứng dụng"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Nhấn vào ứng dụng khác để chia đôi màn hình"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Chọn một ứng dụng khác để dùng chế độ chia đôi màn hình"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Huỷ"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Làm nhiều việc hơn qua Thanh tác vụ"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Luôn hiện Taskbar"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Để luôn hiện Taskbar ở cuối màn hình, hãy nhấn và giữ đường phân chia"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Chạm và giữ phím hành động để tìm nội dung trên màn hình của bạn"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Sản phẩm này dùng phần được chọn trên màn hình để tìm kiếm. Có áp dụng <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Chính sách quyền riêng tư<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> và <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Điều khoản dịch vụ<xliff:g id="END_TOS_LINK"></a></xliff:g> của Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Đóng"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Xong"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Màn hình chính"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Chuyển lên trên cùng/sang bên trái"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Chuyển xuống dưới cùng/sang bên phải"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Hiện thêm # ứng dụng.}other{Hiện thêm # ứng dụng.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Hiện # ứng dụng dành cho máy tính.}other{Hiện # ứng dụng dành cho máy tính.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> và <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Đang thêm ứng dụng vào máy tính"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Huỷ"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bong bóng"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Bong bóng bổ sung"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> từ <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> và <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> bong bóng khác"</string>
</resources>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index f542c9e..f6e446e 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由窗口"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
<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>
@@ -94,7 +95,8 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"系统导航设置"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"分享"</string>
<string name="action_screenshot" msgid="8171125848358142917">"屏幕截图"</string>
- <string name="action_split" msgid="2098009717623550676">"拆分"</string>
+ <string name="action_split" msgid="2098009717623550676">"分屏"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"保存应用组合"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"点按另一个应用即可使用分屏"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"另外选择一个应用才可使用分屏模式"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"取消"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"体验任务栏的更多功能"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"始终显示任务栏"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"若要始终在屏幕底部显示任务栏,请轻触并按住分隔线"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"轻触并按住操作键,即可根据屏幕上的内容进行搜索"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"此产品会根据屏幕上的所选内容进行搜索。Google 的<xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>《隐私权政策》<xliff:g id="END_PRIVACY_LINK"></a></xliff:g>和<xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>《服务条款》<xliff:g id="END_TOS_LINK"></a></xliff:g>适用。"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"关闭"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"完成"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"主屏幕"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"移到顶部/左侧"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移到底部/右侧"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{显示另外 # 个应用。}other{显示另外 # 个应用。}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{显示 # 款桌面应用。}other{显示 # 款桌面应用。}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g>和<xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"将应用添加到桌面"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"取消"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"气泡框"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"溢出式气泡框"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"来自“<xliff:g id="APP_NAME">%2$s</xliff:g>”的<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>以及另外 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> 个"</string>
</resources>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index d01211d..b9d8eb7 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"分享"</string>
<string name="action_screenshot" msgid="8171125848358142917">"螢幕截圖"</string>
<string name="action_split" msgid="2098009717623550676">"分割"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"儲存應用程式組合"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"輕按其他應用程式以使用分割螢幕"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"選擇其他應用程式才能使用分割螢幕"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"取消"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"工作列助你事半功倍"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"一律顯示工作列"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"如要持續在畫面底部顯示工作列,請按住分隔線"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"按住快捷操作鍵即可搜尋畫面上的內容"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"此產品使用螢幕的特定部分進行搜尋。須受 Google 的《<xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>私隱權政策<xliff:g id="END_PRIVACY_LINK"></a></xliff:g>》和《<xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>服務條款<xliff:g id="END_TOS_LINK"></a></xliff:g>》約束。"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"關閉"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"完成"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"住宅"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"移至上方/左側"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移至底部/右側"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{顯示另外 # 個應用程式。}other{顯示另外 # 個應用程式。}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{顯示 # 個桌面應用程式。}other{顯示 # 個桌面應用程式。}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"「<xliff:g id="APP_NAME_1">%1$s</xliff:g>」和「<xliff:g id="APP_NAME_2">%2$s</xliff:g>」"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"正在新增應用程式至桌面"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"取消"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"對話氣泡"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"展開式"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> 的「<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>」通知"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>和其他 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> 則通知"</string>
</resources>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index 142f883..37e7d23 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
<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>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"分享"</string>
<string name="action_screenshot" msgid="8171125848358142917">"螢幕截圖"</string>
<string name="action_split" msgid="2098009717623550676">"分割"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"儲存應用程式配對"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"輕觸另一個應用程式即可使用分割畫面"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"選擇要在分割畫面中使用的另一個應用程式"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"取消"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"充分發揮工作列的功用"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"一律顯示工作列"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"如要一律在畫面底部顯示工作列,請按住分隔線"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"按住快捷操作鍵即可搜尋畫面上的內容"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"這項產品會搜尋你在畫面上選取的內容 (適用 Google 的《<xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>隱私權政策<xliff:g id="END_PRIVACY_LINK"></a></xliff:g>》和《<xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>服務條款<xliff:g id="END_TOS_LINK"></a></xliff:g>》)。"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"關閉"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"完成"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"主畫面"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"移到上方/左側"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移到底部/右側"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{再多顯示 # 個應用程式。}other{再多顯示 # 個應用程式。}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{顯示 # 個電腦版應用程式。}other{顯示 # 個電腦版應用程式。}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"「<xliff:g id="APP_NAME_1">%1$s</xliff:g>」和「<xliff:g id="APP_NAME_2">%2$s</xliff:g>」"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"新增應用程式至桌面"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"取消"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"泡泡"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"溢位"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"「<xliff:g id="APP_NAME">%2$s</xliff:g>」的「<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>」通知"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>和另外 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> 則通知"</string>
</resources>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index bf5a1b7..73be445 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -21,6 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="recent_task_option_pin" msgid="7929860679018978258">"Phina"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"I-Freeform"</string>
+ <string name="recent_task_option_desktop" msgid="8280879717125435668">"Ideskithophu"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Azikho izinto zakamuva"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Izilungiselelo zokusetshenziswa kohlelo lokusebenza"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Sula konke"</string>
@@ -95,6 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Yabelana"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Isithombe-skrini"</string>
<string name="action_split" msgid="2098009717623550676">"Hlukanisa"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Londoloza ukubhangqa i-app"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Thepha enye i-app ukuze usebenzise isikrini sokuhlukanisa"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Khetha enye i-app ukuze usebenzise ukuhlukanisa isikrini"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Khansela"</b></string>
@@ -115,6 +117,8 @@
<string name="taskbar_edu_features" msgid="3320337287472848162">"Yenza okwengeziwe nge-Taskbar"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Bonisa njalo i-Taskbar"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Ukuze ubonise njalo i-Taskbar phansi kwesikrini sakho, thinta bese ubamba isihlukanisi"</string>
+ <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Thinta uphinde ubambe inkinobho yokufinyelela ukuze useshe lokho okusesikrinini sakho"</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Lo mkhiqizo usebenzisa ingxenye ekhethiwe yesikrini sakho ukusesha. <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Inqubomgomo Yobumfihlo<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> ye-Google kanye <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Nemigomo Yesevisi<xliff:g id="END_TOS_LINK"></a></xliff:g> ziyasebenza."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Vala"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Kwenziwe"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Ikhaya"</string>
@@ -134,7 +138,10 @@
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Hamba phezulu/kwesokunxele"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Hamba phansi/kwesokudla"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Bonisa i-app e-# ngaphezulu.}one{Bonisa ama-app angu-# ngaphezulu.}other{Bonisa ama-app angu-# ngaphezulu.}}"</string>
+ <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Bonisa i-app engu-# yedeskithophu.}one{Bonisa ama-app angu-# wedeskithophu.}other{Bonisa ama-app angu-# wedeskithophu.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"I-<xliff:g id="APP_NAME_1">%1$s</xliff:g> ne-<xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
- <string name="desktop_select_app_toast" msgid="2306057322833956910">"Yengeza i-app ku-Deskithophu"</string>
- <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Khansela"</string>
+ <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Ibhamuza"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Ukugcwala kakhulu"</string>
+ <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> kusuka ku-<xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+ <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> nokunye okungu-<xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values/attrs.xml b/quickstep/res/values/attrs.xml
index 7288774..ccc7f18 100644
--- a/quickstep/res/values/attrs.xml
+++ b/quickstep/res/values/attrs.xml
@@ -30,6 +30,11 @@
<attr name="hoverBorderColor" format="color" />
</declare-styleable>
+ <declare-styleable name="ClearAllButton">
+ <!-- focus border color for overview clear all button views -->
+ <attr name="focusBorderColor" />
+ </declare-styleable>
+
<!--
Gesture nav edu specific attributes. These attributes are used to customize Gesture nav edu
view lottie animation colors in XML files.
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 1b5b0ee..14a916f 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -31,6 +31,7 @@
<color name="taskbar_nav_icon_dark_color_on_home">#99000000</color>
<color name="taskbar_stashed_handle_light_color">#EBffffff</color>
<color name="taskbar_stashed_handle_dark_color">#99000000</color>
+ <color name="taskbar_running_app_indicator_color">#646464</color>
<!-- Floating rotation button -->
<color name="floating_rotation_button_light_color">#ffffff</color>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index e45d9fd..fd12210 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -17,8 +17,7 @@
<string name="overscroll_plugin_factory_class" translatable="false" />
<string name="task_overlay_factory_class" translatable="false"/>
- <!-- Activities which block home gesture -->
- <string-array name="gesture_blocking_activities" translatable="false">
+ <string-array name="back_gesture_blocking_activities" translatable="false">
<item>com.android.launcher3/com.android.quickstep.interaction.GestureSandboxActivity</item>
</string-array>
@@ -26,6 +25,21 @@
<string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
<string name="window_manager_proxy_class" translatable="false">com.android.quickstep.util.SystemWindowManagerProxy</string>
<string name="widget_holder_factory_class" translatable="false">com.android.launcher3.uioverrides.QuickstepWidgetHolder$QuickstepHolderFactory</string>
+ <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
+ <string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
+ <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
+ <string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
+ <string name="secondary_display_predictions_class" translatable="false">com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl</string>
+ <string name="taskbar_model_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarModelCallbacksFactory</string>
+ <string name="taskbar_view_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarViewCallbacksFactory</string>
+ <string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
+ <string name="plugin_manager_wrapper_class" translatable="false">com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl</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="assist_utils_class" translatable="false"></string>
+ <string name="assist_state_manager_class" translatable="false"></string>
+ <string name="api_wrapper_class" translatable="false">com.android.launcher3.uioverrides.SystemApiWrapper</string>
<!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
determines how many thumbnails will be fetched in the background. -->
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index f90d96f..5c82c99 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -98,6 +98,8 @@
<dimen name="default_task_dismiss_drag_velocity_grid_focus_task">5dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
+ <dimen name="recents_clear_all_outline_radius">24dp</dimen>
+ <dimen name="recents_clear_all_outline_padding">2dp</dimen>
<!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
loading full resolution screenshots. -->
@@ -175,6 +177,8 @@
<dimen name="gesture_tutorial_menu_done_button_top_spacing">0dp</dimen>
<dimen name="gesture_tutorial_menu_back_shape_bottom_margin">0dp</dimen>
<dimen name="gesture_tutorial_menu_done_button_spacing">72dp</dimen>
+ <dimen name="gesture_tutorial_done_button_bottom_margin">40dp</dimen>
+ <dimen name="gesture_tutorial_done_button_end_margin">24dp</dimen>
<!-- Gesture Tutorial mock conversations -->
<dimen name="gesture_tutorial_message_icon_size">44dp</dimen>
@@ -325,9 +329,9 @@
<dimen name="taskbar_contextual_padding_top">8dp</dimen>
<dimen name="taskbar_nav_buttons_size">44dp</dimen>
<dimen name="taskbar_split_instructions_margin">48dp</dimen>
- <dimen name="taskbar_contextual_button_margin">120dp</dimen>
+ <dimen name="taskbar_ime_switcher_button_margin_start">40dp</dimen>
<dimen name="taskbar_suw_insets">48dp</dimen>
- <dimen name="taskbar_suw_frame">48dp</dimen>
+ <dimen name="taskbar_suw_frame">96dp</dimen>
<dimen name="taskbar_hotseat_nav_spacing">24dp</dimen>
<dimen name="taskbar_contextual_buttons_size">35dp</dimen>
<dimen name="taskbar_stashed_size">24dp</dimen>
@@ -346,12 +350,24 @@
<dimen name="taskbar_icon_size_kids">32dp</dimen>
<dimen name="taskbar_all_apps_button_translation_x_offset">6dp</dimen>
<dimen name="taskbar_all_apps_search_button_translation_x_offset">6dp</dimen>
+ <dimen name="taskbar_contextual_button_suw_margin">64dp</dimen>
+ <dimen name="taskbar_contextual_button_suw_height">64dp</dimen>
+ <dimen name="taskbar_back_button_suw_start_margin">48dp</dimen>
+ <dimen name="taskbar_back_button_suw_bottom_margin">1dp</dimen>
+ <dimen name="taskbar_back_button_suw_height">72dp</dimen>
+ <dimen name="taskbar_running_app_indicator_height">4dp</dimen>
+ <dimen name="taskbar_running_app_indicator_width">14dp</dimen>
+ <dimen name="taskbar_running_app_indicator_top_margin">2dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_height">2dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_width">12dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_top_margin">2dp</dimen>
<!-- Transient taskbar -->
<dimen name="transient_taskbar_padding">12dp</dimen>
<dimen name="transient_taskbar_min_width">150dp</dimen>
<dimen name="transient_taskbar_bottom_margin">24dp</dimen>
<dimen name="transient_taskbar_shadow_blur">40dp</dimen>
+ <dimen name="transient_taskbar_stroke_width">1dp</dimen>
<dimen name="transient_taskbar_key_shadow_distance">10dp</dimen>
<dimen name="transient_taskbar_stashed_height">32dp</dimen>
<dimen name="transient_taskbar_all_apps_button_translation_x_offset">8dp</dimen>
@@ -369,6 +385,11 @@
<!-- Taskbar swipe down threshold -->
<dimen name="taskbar_to_nav_threshold">24dp</dimen>
+ <!-- Taskbar variables that help determine when to animate the Taskbar background -->
+ <!-- Velocity defined as dp per s. Negative because the gesture is an upwards motion. -->
+ <dimen name="taskbar_slow_velocity_y_threshold">-288dp</dimen>
+ <integer name="taskbar_background_duration">80</integer>
+
<!-- Taskbar swipe up threshold multipliers -->
<item name="taskbar_nav_threshold_mult" format="float" type="dimen">4.5</item>
<item name="taskbar_app_window_threshold_mult" format="float" type="dimen">10</item>
@@ -396,6 +417,7 @@
<dimen name="taskbar_edu_features_tooltip_width_with_one_feature">412dp</dimen>
<dimen name="taskbar_edu_features_tooltip_width_with_two_features">428dp</dimen>
<dimen name="taskbar_edu_features_tooltip_width_with_three_features">624dp</dimen>
+ <dimen name="taskbar_edu_search_subtitle_text_size">12sp</dimen>
<!--- Taskbar Pinning -->
<dimen name="taskbar_pinning_popup_menu_width">300dp</dimen>
@@ -412,15 +434,26 @@
<dimen name="bubblebar_stashed_handle_width">55dp</dimen>
<dimen name="bubblebar_stashed_size">@dimen/transient_taskbar_stashed_height</dimen>
<dimen name="bubblebar_stashed_handle_height">@dimen/taskbar_stashed_handle_height</dimen>
- <dimen name="bubblebar_pointer_size">8dp</dimen>
+ <!-- this is a pointer height minus 1dp to ensure the pointer overlaps with the bubblebar
+ background appropriately when close to the rounded corner -->
+ <dimen name="bubblebar_pointer_visible_size">9dp</dimen>
+ <dimen name="bubblebar_pointer_width">12dp</dimen>
+ <dimen name="bubblebar_pointer_height">10dp</dimen>
+ <dimen name="bubblebar_pointer_radius">2dp</dimen>
+ <!-- Container size with pointer included: bubblebar_size + bubblebar_pointer_size -->
+ <dimen name="bubblebar_size_with_pointer">80dp</dimen>
<dimen name="bubblebar_elevation">1dp</dimen>
+ <dimen name="bubblebar_drag_elevation">2dp</dimen>
<dimen name="bubblebar_hotseat_adjustment_threshold">90dp</dimen>
- <dimen name="bubblebar_icon_size">50dp</dimen>
+ <dimen name="bubblebar_icon_size_small">32dp</dimen>
+ <dimen name="bubblebar_icon_size">36dp</dimen>
<dimen name="bubblebar_badge_size">24dp</dimen>
<dimen name="bubblebar_icon_overlap">12dp</dimen>
- <dimen name="bubblebar_overflow_inset">24dp</dimen>
- <dimen name="bubblebar_icon_spacing">3dp</dimen>
+ <dimen name="bubblebar_overflow_inset">16dp</dimen>
+ <dimen name="bubblebar_icon_spacing">6dp</dimen>
+ <dimen name="bubblebar_icon_spacing_large">8dp</dimen>
+ <dimen name="bubblebar_expanded_icon_spacing">12dp</dimen>
<dimen name="bubblebar_icon_elevation">1dp</dimen>
<!-- Bubble bar dismiss view -->
@@ -429,6 +462,16 @@
<dimen name="bubblebar_dismiss_target_icon_size">24dp</dimen>
<dimen name="bubblebar_dismiss_target_bottom_margin">50dp</dimen>
<dimen name="bubblebar_dismiss_floating_gradient_height">548dp</dimen>
+ <dimen name="bubblebar_dismiss_zone_width">192dp</dimen>
+ <dimen name="bubblebar_dismiss_zone_height">242dp</dimen>
+
+ <!-- Bubble bar drop target -->
+ <dimen name="bubblebar_drop_target_corner_radius">36dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_default_width">412dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_default_height">600dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_corner_radius">28dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_padding">24dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_margin">16dp</dimen>
<!-- Launcher splash screen -->
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
@@ -450,11 +493,11 @@
<dimen name="keyboard_quick_switch_no_recent_items_icon_size">24dp</dimen>
<dimen name="keyboard_quick_switch_no_recent_items_icon_margin">8dp</dimen>
- <!-- Desktop mode -->
- <dimen name="desktop_mode_floating_app_select_height">56dp</dimen>
- <dimen name="desktop_mode_floating_app_select_elevation">4dp</dimen>
- <dimen name="desktop_mode_floating_app_select_margin">16dp</dimen>
- <dimen name="desktop_mode_floating_app_select_text_size">14sp</dimen>
- <dimen name="desktop_mode_floating_app_select_text_margin">8dp</dimen>
+ <!-- Digital Wellbeing -->
+ <dimen name="digital_wellbeing_toast_height">48dp</dimen>
+
+ <!-- Splitscreen -->
+ <!-- Max width of the split instructions view -->
+ <dimen name="split_instructions_view_max_width">220dp</dimen>
</resources>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
deleted file mode 100644
index cba1f5b..0000000
--- a/quickstep/res/values/override.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<!-- Class overrides for launcher with quickstep. -->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
-
- <string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
-
- <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
-
- <string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
-
- <string name="nav_handle_long_press_handler_class" translatable="false"></string>
-
- <string name="assist_utils_class" translatable="false"></string>
-
- <string name="secondary_display_predictions_class" translatable="false">com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl</string>
-
- <string name="taskbar_model_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarModelCallbacksFactory</string>
-
- <string name="taskbar_view_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarViewCallbacksFactory</string>
-
- <string name="assist_state_manager_class" translatable="false"></string>
-
- <string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
-
-</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 14f615e..340d25b 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -26,6 +26,8 @@
<string name="recent_task_option_pin">Pin</string>
<!-- Title for an option to enter freeform mode for a given app -->
<string name="recent_task_option_freeform">Freeform</string>
+ <!-- Title for an option to enter desktop windowing mode for a given app -->
+ <string name="recent_task_option_desktop">Desktop</string>
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">No recent items</string>
@@ -230,6 +232,8 @@
<string name="action_screenshot">Screenshot</string>
<!-- Label for a button that enters split screen selection mode. [CHAR_LIMIT=20] -->
<string name="action_split">Split</string>
+ <!-- Label for a button that saves a new app pair. [CHAR_LIMIT=20] -->
+ <string name="action_save_app_pair">Save app pair</string>
<!-- Label for toast with instructions for split screen selection mode. [CHAR_LIMIT=50] -->
<string name="toast_split_select_app">Tap another app to use split screen</string>
<string name="toast_contextual_split_select_app">Choose another app to use split screen</string>
@@ -271,6 +275,10 @@
<string name="taskbar_edu_pinning_title">Always show the Taskbar</string>
<!-- Text in dialog that shows a user how to pin the Taskbar. [CHAR_LIMIT 150] -->
<string name="taskbar_edu_pinning_standalone">To always show the Taskbar on the bottom of your screen, touch & hold the divider</string>
+ <!-- Title in dialog that shows a user how to invoke the Search feature. [CHAR_LIMIT 150] -->
+ <string name="taskbar_search_edu_title">Touch & hold the action key to search what\'s on your screen</string>
+ <!-- Message showed to user to disclose privacy information they need to accept in order to access the app. [CHAR LIMIT=200]-->
+ <string name="taskbar_edu_search_disclosure">This product uses the selected part of your screen to search. Google\'s <xliff:g example="https://policies.google.com/privacy/embedded" id="begin_privacy_link"><a href=\"%1$s\"></xliff:g>Privacy Policy<xliff:g id="end_privacy_link"></a></xliff:g> and <xliff:g example="https://policies.google.com/terms" id="begin_tos_link"><a href=\"%2$s\"></xliff:g>Terms of Service<xliff:g id="end_tos_link"></a></xliff:g> apply.</string>
<!-- Text on button to exit a tutorial [CHAR_LIMIT=16] -->
<string name="taskbar_edu_close">Close</string>
<!-- Text on button to finish a tutorial [CHAR_LIMIT=16] -->
@@ -316,12 +324,22 @@
other{Show # more apps.}
}</string>
+ <!-- Label for quick switch tile showing how many apps are available in desktop mode [CHAR LIMIT=NONE] -->
+ <string name="quick_switch_desktop">{count, plural,
+ =1{Show # desktop app.}
+ other{Show # desktop apps.}
+ }</string>
+
<!-- Accessibility label for quick switch tiles showing split tasks [CHAR LIMIT=NONE] -->
<string name="quick_switch_split_task"><xliff:g id="app_name_1" example="Chrome">%1$s</xliff:g> and <xliff:g id="app_name_2" example="Gmail">%2$s</xliff:g></string>
- <!-- ******* Desktop ******* -->
- <!-- Text shown in popup to choose a desktop app. [CHAR LIMIT=60] -->
- <string name="desktop_select_app_toast">Adding app to Desktop</string>
- <!-- Text shown on a button that closes the popup for choosing a desktop app. [CHAR_LIMIT=40] -->
- <string name="desktop_button_close_app_toast">Cancel</string>
+ <!-- Strings for bubble bar -->
+ <!-- Fallback name for a bubble if it does have a title [CHAR_LIMIT=none] -->
+ <string name="bubble_bar_bubble_fallback_description">Bubble</string>
+ <!-- content description for the overflow bubble [CHAR_LIMIT=none] -->
+ <string name="bubble_bar_overflow_description">Overflow</string>
+ <!-- Content description for a bubble bar bubble. [CHAR_LIMIT=none] -->
+ <string name="bubble_bar_bubble_description"><xliff:g id="notification_title" example="some title">%1$s</xliff:g> from <xliff:g id="app_name" example="YouTube">%2$s</xliff:g></string>
+ <!-- Content description for bubble bar when it has multiple bubbles. [CHAR_LIMIT=NONE] -->
+ <string name="bubble_bar_description_multiple_bubbles"><xliff:g id="bubble_bar_bubble_description" example="some title from YouTube">%1$s</xliff:g> and <xliff:g id="bubble_count" example="4">%2$d</xliff:g> more</string>
</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 350c752..952505a 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -169,17 +169,17 @@
<style name="TextAppearance.GestureTutorial.MainTitle.Home"
parent="TextAppearance.GestureTutorial.MainTitle">
- <item name="android:textColor">?attr/onSurfaceHome</item>
+ <item name="android:textColor">?attr/secondaryHome</item>
</style>
<style name="TextAppearance.GestureTutorial.MainTitle.Back"
parent="TextAppearance.GestureTutorial.MainTitle">
- <item name="android:textColor">?attr/onSurfaceBack</item>
+ <item name="android:textColor">?attr/secondaryBack</item>
</style>
<style name="TextAppearance.GestureTutorial.MainTitle.Overview"
parent="TextAppearance.GestureTutorial.MainTitle">
- <item name="android:textColor">?attr/onSurfaceOverview</item>
+ <item name="android:textColor">?attr/secondaryOverview</item>
</style>
<style name="TextAppearance.GestureTutorial.MainTitle.Success.Home"
@@ -315,5 +315,6 @@
<style name="WidgetPickerActivityTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
<item name="android:windowBackground">@android:color/transparent</item>
+ <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_light</item>
</style>
</resources>
diff --git a/quickstep/src/com/android/launcher3/HomeTransitionController.java b/quickstep/src/com/android/launcher3/HomeTransitionController.java
deleted file mode 100644
index 2b50283..0000000
--- a/quickstep/src/com/android/launcher3/HomeTransitionController.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.quickstep.SystemUiProxy;
-import com.android.wm.shell.transition.IHomeTransitionListener;
-
-/**
- * Controls launcher response to home activity visibility changing.
- */
-public class HomeTransitionController {
-
- @Nullable private QuickstepLauncher mLauncher;
- @Nullable private IHomeTransitionListener mHomeTransitionListener;
-
- public void registerHomeTransitionListener(QuickstepLauncher launcher) {
- mLauncher = launcher;
- mHomeTransitionListener = new IHomeTransitionListener.Stub() {
- @Override
- public void onHomeVisibilityChanged(boolean isVisible) {
- MAIN_EXECUTOR.execute(() -> {
- if (mLauncher != null && mLauncher.getTaskbarUIController() != null) {
- mLauncher.getTaskbarUIController().onLauncherVisibilityChanged(isVisible);
- }
- });
- }
- };
-
- SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(mHomeTransitionListener);
- }
-
- public void unregisterHomeTransitionListener() {
- SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(null);
- mHomeTransitionListener = null;
- mLauncher = null;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 75b8796c..fae281a 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -43,6 +43,7 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -57,6 +58,7 @@
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
@@ -64,6 +66,7 @@
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
+import static com.android.quickstep.util.AnimUtils.clampToDuration;
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
@@ -95,7 +98,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
-import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.view.CrossWindowBlurListeners;
@@ -149,6 +151,7 @@
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.RectFSpringAnim.DefaultSpringConfig;
import com.android.quickstep.util.RectFSpringAnim.TaskbarHotseatSpringConfig;
+import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransaction;
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
@@ -215,7 +218,8 @@
public static final int TASKBAR_TO_APP_DURATION = 600;
// TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation
// is solved.
- public static final int TASKBAR_TO_HOME_DURATION = 300;
+ private static final int TASKBAR_TO_HOME_DURATION_FAST = 300;
+ private static final int TASKBAR_TO_HOME_DURATION_SLOW = 1000;
protected static final int CONTENT_SCALE_DURATION = 350;
protected static final int CONTENT_SCRIM_DURATION = 350;
@@ -371,7 +375,7 @@
*/
protected boolean isLaunchingFromRecents(@NonNull View v,
@Nullable RemoteAnimationTarget[] targets) {
- return mLauncher.getStateManager().getState().overviewUi
+ return mLauncher.getStateManager().getState().isRecentsViewVisible
&& findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null;
}
@@ -748,34 +752,35 @@
final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius;
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
- FloatProp mDx = new FloatProp(0, prop.dX, 0, APP_LAUNCH_DURATION,
- mOpeningXInterpolator);
- FloatProp mDy = new FloatProp(0, prop.dY, 0, APP_LAUNCH_DURATION,
- mOpeningInterpolator);
+ FloatProp mDx = new FloatProp(0, prop.dX, mOpeningXInterpolator);
+ FloatProp mDy = new FloatProp(0, prop.dY, mOpeningInterpolator);
FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale,
- prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
+ prop.finalAppIconScale, mOpeningInterpolator);
FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f,
- APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, LINEAR);
+ clampToDuration(LINEAR, APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION,
+ APP_LAUNCH_DURATION));
- FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0,
- APP_LAUNCH_DURATION, mOpeningInterpolator);
- FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0,
- APP_LAUNCH_DURATION, mOpeningInterpolator);
+ FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
+ mOpeningInterpolator);
+ FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius,
+ mOpeningInterpolator);
FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd,
- 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
+ mOpeningInterpolator);
FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd,
- 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
- FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 0,
- APP_LAUNCH_DURATION, mOpeningInterpolator);
- FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0,
- APP_LAUNCH_DURATION, mOpeningInterpolator);
+ mOpeningInterpolator);
+ FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd,
+ mOpeningInterpolator);
+ FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd,
+ mOpeningInterpolator);
- FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
- NAV_FADE_OUT_INTERPOLATOR);
- FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN,
- ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR);
+ FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration(
+ NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION,
+ APP_LAUNCH_DURATION));
+ FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration(
+ NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN,
+ ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION));
@Override
public void onUpdate(float percent, boolean initOnly) {
@@ -968,37 +973,36 @@
appAnimator.addUpdateListener(new MultiValueUpdateListener() {
float mAppWindowScale = 1;
- final FloatProp mWidgetForegroundAlpha = new FloatProp(1 /* start */,
- 0 /* end */, 0 /* delay */,
- WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
- final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0 /* start */,
- 1 /* end */, 0 /* delay */, 75 /* duration */, LINEAR);
- final FloatProp mPreviewAlpha = new FloatProp(0 /* start */, 1 /* end */,
+ final FloatProp mWidgetForegroundAlpha = new FloatProp(1, 0, clampToDuration(
+ LINEAR, 0, WIDGET_CROSSFADE_DURATION_MILLIS / 2, APP_LAUNCH_DURATION));
+
+ final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0, 1,
+ clampToDuration(LINEAR, 0, 75, APP_LAUNCH_DURATION));
+ final FloatProp mPreviewAlpha = new FloatProp(0, 1, clampToDuration(
+ LINEAR,
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */,
- WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
+ WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */,
+ APP_LAUNCH_DURATION));
final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
- 0 /* start */, APP_LAUNCH_DURATION, mOpeningInterpolator);
- final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, APP_LAUNCH_DURATION,
mOpeningInterpolator);
+ final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, mOpeningInterpolator);
// Window & widget background positioning bounds
final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(),
- windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_DURATION,
- mOpeningXInterpolator);
+ windowTargetBounds.centerX(), mOpeningXInterpolator);
final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(),
- windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION,
- mOpeningInterpolator);
+ windowTargetBounds.centerY(), mOpeningInterpolator);
final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(),
- windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION,
- mOpeningInterpolator);
+ windowTargetBounds.width(), mOpeningInterpolator);
final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(),
- windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION,
- mOpeningInterpolator);
+ windowTargetBounds.height(), mOpeningInterpolator);
- final FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
- NAV_FADE_OUT_INTERPOLATOR);
- final FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN,
- ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR);
+ final FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration(
+ NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION,
+ APP_LAUNCH_DURATION));
+ final FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration(
+ NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN,
+ ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION));
@Override
public void onUpdate(float percent, boolean initOnly) {
@@ -1508,11 +1512,10 @@
float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
closingAnimator.setDuration(duration);
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
- FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DECELERATE_1_7);
- FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DECELERATE_1_7);
- FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
- FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, 0, duration,
- DECELERATE_1_7);
+ FloatProp mDy = new FloatProp(0, mClosingWindowTransY, DECELERATE_1_7);
+ FloatProp mScale = new FloatProp(1f, 1f, DECELERATE_1_7);
+ FloatProp mAlpha = new FloatProp(1f, 0f, clampToDuration(LINEAR, 25, 125, duration));
+ FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, DECELERATE_1_7);
@Override
public void onUpdate(float percent, boolean initOnly) {
@@ -1627,10 +1630,15 @@
anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
} else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
&& !playFallBackAnimation) {
- // Use a fixed velocity to start the animation.
- float velocityPxPerS = DynamicResource.provider(mLauncher)
- .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
- PointF velocity = new PointF(0, -velocityPxPerS);
+ PointF velocity;
+ if (enableScalingRevealHomeAnimation()) {
+ velocity = new PointF();
+ } else {
+ // Use a fixed velocity to start the animation.
+ float velocityPxPerS = DynamicResource.provider(mLauncher)
+ .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
+ velocity = new PointF(0, -velocityPxPerS);
+ }
rectFSpringAnim = getClosingWindowAnimators(
anim, appTargets, launcherView, velocity, startRect,
startWindowCornerRadius);
@@ -1639,8 +1647,15 @@
// layout bounds.
skipAllAppsScale = true;
} else if (!fromPredictiveBack) {
- anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
- true /* animateOverviewScrim */, launcherView).getAnimators());
+ if (enableScalingRevealHomeAnimation()) {
+ anim.play(
+ new ScalingWorkspaceRevealAnim(
+ mLauncher, rectFSpringAnim,
+ rectFSpringAnim.getTargetRect()).getAnimators());
+ } else {
+ anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
+ true /* animateOverviewScrim */, launcherView).getAnimators());
+ }
if (!areAllTargetsTranslucent(appTargets)) {
anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth,
@@ -1705,6 +1720,14 @@
return new Pair(rectFSpringAnim, anim);
}
+ public static int getTaskbarToHomeDuration() {
+ if (enableScalingRevealHomeAnimation()) {
+ return TASKBAR_TO_HOME_DURATION_SLOW;
+ } else {
+ return TASKBAR_TO_HOME_DURATION_FAST;
+ }
+ }
+
/**
* Remote animation runner for animation from the app to Launcher, including recents.
*/
@@ -1776,7 +1799,6 @@
RemoteAnimationTarget[] wallpaperTargets,
RemoteAnimationTarget[] nonAppTargets,
LauncherAnimationRunner.AnimationResult result) {
- Log.d("b/318394698", "AppLaunchAnimationRunner: onAnimationStart");
AnimatorSet anim = new AnimatorSet();
boolean launcherClosing =
launcherIsATargetWithMode(appTargets, MODE_CLOSING);
@@ -1812,7 +1834,6 @@
@Override
public void onAnimationCancelled() {
- Log.d("b/318394698", "AppLaunchAnimationRunner: onAnimationCancelled");
mOnEndCallback.executeAllAndDestroy();
}
}
@@ -1873,7 +1894,7 @@
return new ContainerAnimationRunner(
new ActivityTransitionAnimator.AnimationDelegate(
- controller, callback, listener));
+ MAIN_EXECUTOR, controller, callback, listener));
}
/**
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 8c4db4a..943c08c 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -35,23 +35,31 @@
import android.view.WindowManager;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.dragndrop.SimpleDragLayer;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.WidgetPredictionsRequester;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
/** An Activity that can host Launcher's widget picker. */
public class WidgetPickerActivity extends BaseActivity {
private static final String TAG = "WidgetPickerActivity";
-
/**
* Name of the extra that indicates that a widget being dragged.
*
@@ -64,14 +72,33 @@
// 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";
-
+ /**
+ * Widgets currently added by the user in the UI surface.
+ * <p>This allows widget picker to exclude existing widgets from suggestions.</p>
+ */
+ private static final String EXTRA_ADDED_APP_WIDGETS = "added_app_widgets";
+ /**
+ * A unique identifier of the surface hosting the widgets;
+ * <p>"widgets" is reserved for home screen surface.</p>
+ * <p>"widgets_hub" is reserved for glanceable hub surface.</p>
+ */
+ private static final String EXTRA_UI_SURFACE = "ui_surface";
+ private static final Pattern UI_SURFACE_PATTERN =
+ Pattern.compile("^(widgets|widgets_hub)$");
private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
private WidgetsModel mModel;
+ private LauncherAppState mApp;
+ private WidgetPredictionsRequester mWidgetPredictionsRequester;
private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
private int mDesiredWidgetWidth;
private int mDesiredWidgetHeight;
private int mWidgetCategoryFilter;
+ @Nullable
+ private String mUiSurface;
+ // Widgets existing on the host surface.
+ @NonNull
+ private List<AppWidgetProviderInfo> mAddedWidgets = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -80,9 +107,8 @@
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
- LauncherAppState app = LauncherAppState.getInstance(this);
- InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
-
+ mApp = LauncherAppState.getInstance(this);
+ InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
mDeviceProfile = idp.getDeviceProfile(this);
mModel = new WidgetsModel();
@@ -97,6 +123,11 @@
widgetSheet.disableNavBarScrim(true);
widgetSheet.addOnCloseListener(this::finish);
+ parseIntentExtras();
+ refreshAndBindWidgets();
+ }
+
+ private void parseIntentExtras() {
// A value of 0 for either size means that no filtering will occur in that dimension. If
// both values are 0, then no size filtering will occur.
mDesiredWidgetWidth =
@@ -108,7 +139,15 @@
mWidgetCategoryFilter =
getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
- refreshAndBindWidgets();
+ String uiSurfaceParam = getIntent().getStringExtra(EXTRA_UI_SURFACE);
+ if (uiSurfaceParam != null && UI_SURFACE_PATTERN.matcher(uiSurfaceParam).matches()) {
+ mUiSurface = uiSurfaceParam;
+ }
+ ArrayList<AppWidgetProviderInfo> addedWidgets = getIntent().getParcelableArrayListExtra(
+ EXTRA_ADDED_APP_WIDGETS, AppWidgetProviderInfo.class);
+ if (addedWidgets != null) {
+ mAddedWidgets = addedWidgets;
+ }
}
@NonNull
@@ -155,6 +194,11 @@
return false;
}
+ View dragView = widgetCell.getDragAndDropView();
+ if (dragView == null) {
+ return false;
+ }
+
ClipData clipData = new ClipData(
new ClipDescription(
/* label= */ "", // not displayed anywhere; so, set to empty.
@@ -170,20 +214,21 @@
.putExtra(EXTRA_IS_PENDING_WIDGET_DRAG, true));
// DRAG_FLAG_GLOBAL permits dragging data beyond app window.
- return view.startDragAndDrop(
+ return dragView.startDragAndDrop(
clipData,
- new View.DragShadowBuilder(view),
+ new View.DragShadowBuilder(dragView),
/* myLocalState= */ null,
View.DRAG_FLAG_GLOBAL
);
};
}
+ /** Updates the model with widgets and provides them after applying the provided filter. */
private void refreshAndBindWidgets() {
MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
mModel.update(app, null);
- final ArrayList<WidgetsListBaseEntry> widgets =
+ final List<WidgetsListBaseEntry> allWidgets =
mModel.getFilteredWidgetsListForPicker(
app.getContext(),
/*widgetItemFilter=*/ widget -> {
@@ -193,10 +238,37 @@
return verdict.isAcceptable;
}
);
- MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
+ bindWidgets(allWidgets);
+ if (mUiSurface != null) {
+ Map<PackageUserKey, List<WidgetItem>> allWidgetsMap = allWidgets.stream()
+ .filter(WidgetsListHeaderEntry.class::isInstance)
+ .collect(Collectors.toMap(
+ entry -> PackageUserKey.fromPackageItemInfo(entry.mPkgItem),
+ entry -> entry.mWidgets)
+ );
+ mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
+ mUiSurface, allWidgetsMap);
+ mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
+ }
});
}
+ private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
+ MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
+ }
+
+ private void bindRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
+ MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets));
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mWidgetPredictionsRequester != null) {
+ mWidgetPredictionsRequester.clear();
+ }
+ }
+
private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) {
final AppWidgetProviderInfo info = widget.widgetInfo;
if (info == null) {
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 694475a..84c2ed2 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -28,14 +28,15 @@
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
+import android.view.accessibility.AccessibilityManager;
import androidx.annotation.ColorInt;
import androidx.core.content.ContextCompat;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.util.Themes;
/**
* A view which shows a horizontal divider
@@ -54,6 +55,7 @@
private final @ColorInt int mStrokeColor;
private final @ColorInt int mAllAppsLabelTextColor;
+ private final AccessibilityManager mAccessibilityManager;
private Layout mAllAppsLabelLayout;
private boolean mShowAllAppsLabel;
@@ -87,7 +89,8 @@
mAllAppsLabelTextColor = ContextCompat.getColor(context,
R.color.material_color_on_surface_variant);
- mShowAllAppsLabel = !ALL_APPS_VISITED_COUNT.hasReachedMax(context);
+ mAccessibilityManager = AccessibilityManager.getInstance(context);
+ setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(context));
}
public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
@@ -99,6 +102,9 @@
/** {@code true} if all apps label should be shown in place of divider. */
public void setShowAllAppsLabel(boolean showAllAppsLabel) {
+ if (mAccessibilityManager.isEnabled() && !Utilities.isRunningInTestHarness()) {
+ showAllAppsLabel = true;
+ }
if (showAllAppsLabel != mShowAllAppsLabel) {
mShowAllAppsLabel = showAllAppsLabel;
updateDividerType();
@@ -148,6 +154,7 @@
mDividerType = dividerType;
int topPadding;
int bottomPadding;
+ setContentDescription(null);
switch (dividerType) {
case LINE:
topPadding = 0;
@@ -161,6 +168,7 @@
bottomPadding = getResources()
.getDimensionPixelSize(R.dimen.all_apps_label_bottom_padding);
mPaint.setColor(mAllAppsLabelTextColor);
+ setContentDescription(mAllAppsLabelLayout.getText());
break;
case NONE:
default:
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index b36fd66..a16031d 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -37,7 +37,6 @@
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.model.data.ItemInfo;
@@ -45,6 +44,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.views.ActivityContext;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -240,7 +240,11 @@
icon.reset();
if (predictionCount > i) {
icon.setVisibility(View.VISIBLE);
- icon.applyFromWorkspaceItem(mPredictedApps.get(i));
+ WorkspaceItemInfo predictedItem = mPredictedApps.get(i);
+ predictedItem.rank = i;
+ predictedItem.cellX = i;
+ predictedItem.cellY = 0;
+ icon.applyFromWorkspaceItem(predictedItem);
} else {
icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
}
@@ -281,4 +285,11 @@
return getChildAt(0);
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "PredictionRowView");
+ writer.println(prefix + "\tmPredictionsEnabled: " + mPredictionsEnabled);
+ writer.println(prefix + "\tmPredictionUiUpdatePaused: " + mPredictionUiUpdatePaused);
+ writer.println(prefix + "\tmNumPredictedAppsPerRow: " + mNumPredictedAppsPerRow);
+ writer.println(prefix + "\tmPredictedApps: " + mPredictedApps);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index 10733fb..9178062 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -20,9 +20,9 @@
import android.os.RemoteException
import android.util.Log
import android.view.SurfaceControl
-import android.window.IRemoteTransition
import android.window.IRemoteTransitionFinishedCallback
import android.window.RemoteTransition
+import android.window.RemoteTransitionStub
import android.window.TransitionInfo
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
@@ -30,11 +30,12 @@
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.views.DesktopTaskView
+import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import java.util.function.Consumer
/** Manage recents related operations with desktop tasks */
class DesktopRecentsTransitionController(
- private val stateManager: StateManager<*>,
+ private val stateManager: StateManager<*, *>,
private val systemUiProxy: SystemUiProxy,
private val appThread: IApplicationThread,
private val depthController: DepthController?
@@ -56,12 +57,17 @@
systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
}
+ /** Launch desktop tasks from recents view */
+ fun moveToDesktop(taskId: Int, transitionSource: DesktopModeTransitionSource) {
+ systemUiProxy.moveToDesktop(taskId, transitionSource)
+ }
+
private class RemoteDesktopLaunchTransitionRunner(
private val desktopTaskView: DesktopTaskView,
- private val stateManager: StateManager<*>,
+ private val stateManager: StateManager<*, *>,
private val depthController: DepthController?,
private val successCallback: Consumer<Boolean>?
- ) : IRemoteTransition.Stub() {
+ ) : RemoteTransitionStub() {
override fun startAnimation(
token: IBinder,
@@ -90,17 +96,6 @@
}
}
}
-
- override fun mergeAnimation(
- transition: IBinder,
- info: TransitionInfo,
- t: SurfaceControl.Transaction,
- mergeTarget: IBinder,
- finishCallback: IRemoteTransitionFinishedCallback
- ) {}
-
- override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
- }
}
companion object {
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index b903c4e..de974ec 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -190,7 +190,6 @@
}
private void fillGapsWithPrediction(boolean animate) {
- Log.d(TAG, "fillGapsWithPrediction flags: " + getStateString(mPauseFlags));
if (mPauseFlags != 0) {
return;
}
@@ -215,16 +214,12 @@
View child = mHotseat.getChildAt(
mHotseat.getCellXFromOrder(rank),
mHotseat.getCellYFromOrder(rank));
- Log.d(TAG, "Hotseat app child is: " + child + " and isPredictedIcon() evaluates to"
- + ": " + isPredictedIcon(child));
if (child != null && !isPredictedIcon(child)) {
continue;
}
if (mPredictedItems.size() <= predictionIndex) {
// Remove predicted apps from the past
- Log.d(TAG, "Remove predicted apps from the past\nPrediction Index: "
- + predictionIndex);
if (isPredictedIcon(child)) {
mHotseat.removeView(child);
}
@@ -232,11 +227,6 @@
}
WorkspaceItemInfo predictedItem =
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
- Log.d(TAG, "Predicted item is: " + predictedItem);
- if (child != null) {
- Log.d(TAG, "Predicted item is enabled: " + child.isEnabled());
- }
-
if (isPredictedIcon(child) && child.isEnabled()) {
PredictedAppIcon icon = (PredictedAppIcon) child;
boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
@@ -256,7 +246,6 @@
}
private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate) {
- Log.d(TAG, "bindItems to hotseat: " + itemsToAdd);
AnimatorSet animationSet = new AnimatorSet();
for (WorkspaceItemInfo item : itemsToAdd) {
PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
@@ -296,7 +285,6 @@
* start and pauses predicted apps update on the hotseat
*/
public void setPauseUIUpdate(boolean paused) {
- Log.d(TAG, "setPauseUIUpdate parameter `paused` is " + paused);
mPauseFlags = paused
? (mPauseFlags | FLAG_UPDATE_PAUSED)
: (mPauseFlags & ~FLAG_UPDATE_PAUSED);
@@ -306,15 +294,23 @@
}
/**
+ * Ensures that if the flag FLAG_UPDATE_PAUSED is active we set it to false.
+ */
+ public void verifyUIUpdateNotPaused() {
+ if ((mPauseFlags & FLAG_UPDATE_PAUSED) != 0) {
+ setPauseUIUpdate(false);
+ Log.e(TAG, "FLAG_UPDATE_PAUSED should not be set to true (see b/339700174)");
+ }
+ }
+
+ /**
* Sets or updates the predicted items
*/
public void setPredictedItems(FixedContainerItems items) {
mPredictedItems = new ArrayList(items.items);
if (mPredictedItems.isEmpty()) {
- Log.d(TAG, "Predicted items is initially empty");
HotseatRestoreHelper.restoreBackup(mLauncher);
}
- Log.d(TAG, "Predicted items: " + mPredictedItems);
fillGapsWithPrediction();
}
@@ -515,7 +511,7 @@
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
pinPrediction(mItemInfo);
}
}
@@ -537,7 +533,7 @@
}
public void dump(String prefix, PrintWriter writer) {
- writer.println(prefix + this.getClass().getSimpleName());
+ writer.println(prefix + "HotseatPredictionController");
writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags));
writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount);
writer.println(prefix + "\tmPredictedItems: " + mPredictedItems);
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index a931f36..a621259 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -41,6 +41,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_ADD_BUTTON_TAP;
import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction;
import static com.android.launcher3.model.PredictionHelper.isTrackedForWidgetPrediction;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -184,6 +185,10 @@
sendEvent(target, atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION);
} else if (event == LAUNCHER_DISMISS_PREDICTION_UNDO) {
sendEvent(atomInfo, ACTION_UNDISMISS, CONTAINER_HOTSEAT_PREDICTION);
+ } else if (event == LAUNCHER_WIDGET_ADD_BUTTON_TAP) {
+ if (isTrackedForWidgetPrediction(atomInfo)) {
+ sendEvent(atomInfo, ACTION_PIN, CONTAINER_WIDGETS_PREDICTION);
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
index 2fcbe4e..d604742 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -15,8 +15,8 @@
*/
package com.android.launcher3.model;
-import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
import static com.android.launcher3.EncryptionType.ENCRYPTED;
+import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
@@ -32,6 +32,7 @@
import com.android.launcher3.ConstantItem;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
@@ -47,7 +48,7 @@
/**
* Task to update model as a result of predicted apps update
*/
-public class PredictionUpdateTask extends BaseModelUpdateTask {
+public class PredictionUpdateTask implements ModelUpdateTask {
public static final ConstantItem<Boolean> LAST_PREDICTION_ENABLED =
nonRestorableItem("last_prediction_enabled_state", true, ENCRYPTED);
@@ -61,8 +62,9 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
+ LauncherAppState app = taskController.getApp();
Context context = app.getContext();
// TODO: remove this
@@ -119,7 +121,7 @@
FixedContainerItems fci = new FixedContainerItems(mPredictorState.containerId, items);
dataModel.extraItems.put(fci.containerId, fci);
- bindExtraContainerItems(fci);
+ taskController.bindExtraContainerItems(fci);
usersForChangedShortcuts.forEach(
u -> dataModel.updateShortcutPinnedState(app.getContext(), u));
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 0ce1cb8..8b5ed7c 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -18,8 +18,8 @@
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.formatElapsedTime;
-import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
import static com.android.launcher3.EncryptionType.ENCRYPTED;
+import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
@@ -65,13 +65,15 @@
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PersistedItemArray;
import com.android.quickstep.logging.SettingsChangeLogger;
import com.android.quickstep.logging.StatsLogCompatManager;
@@ -111,12 +113,11 @@
private final InvariantDeviceProfile mIDP;
private final AppEventProducer mAppEventProducer;
private final StatsManager mStatsManager;
- private final Context mContext;
protected boolean mActive = false;
public QuickstepModelDelegate(Context context) {
- mContext = context;
+ super(context);
mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
mIDP = InvariantDeviceProfile.INSTANCE.get(context);
@@ -150,7 +151,8 @@
// TODO: Implement caching and preloading
WorkspaceItemFactory factory =
- new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, numColumns, state.containerId);
+ new WorkspaceItemFactory(mApp, ums, mPmHelper, pinnedShortcuts, numColumns,
+ state.containerId);
FixedContainerItems fci = new FixedContainerItems(state.containerId,
state.storage.read(mApp.getContext(), factory, ums.allUsers::get));
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
@@ -233,7 +235,7 @@
}
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
for (ItemInfo info : itemsIdMap) {
- FolderInfo parent = getContainer(info, itemsIdMap);
+ CollectionInfo parent = getContainer(info, itemsIdMap);
StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
}
additionalSnapshotEvents(instanceId);
@@ -270,7 +272,7 @@
}
for (ItemInfo info : itemsIdMap) {
- FolderInfo parent = getContainer(info, itemsIdMap);
+ CollectionInfo parent = getContainer(info, itemsIdMap);
LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
Log.d(TAG, itemInfo.toString());
StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
@@ -293,18 +295,19 @@
}
}
- private static FolderInfo getContainer(ItemInfo info, IntSparseArrayMap<ItemInfo> itemsIdMap) {
+ private static CollectionInfo getContainer(
+ ItemInfo info, IntSparseArrayMap<ItemInfo> itemsIdMap) {
if (info.container > 0) {
ItemInfo containerInfo = itemsIdMap.get(info.container);
- if (!(containerInfo instanceof FolderInfo)) {
+ if (!(containerInfo instanceof CollectionInfo)) {
Log.e(TAG, String.format(
"Item info: %s found with invalid container: %s",
info,
containerInfo));
}
// Allow crash to help debug b/173838775
- return (FolderInfo) containerInfo;
+ return (CollectionInfo) containerInfo;
}
return null;
}
@@ -325,8 +328,12 @@
super.destroy();
mActive = false;
StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
- if (mIsPrimaryInstance) {
- mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
+ if (mIsPrimaryInstance && mStatsManager != null) {
+ try {
+ mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Failed to unregister snapshot logging callback with StatsManager", e);
+ }
}
destroyPredictors();
}
@@ -525,6 +532,7 @@
private final LauncherAppState mAppState;
private final UserManagerState mUMS;
+ private final PackageManagerHelper mPmHelper;
private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
private final int mMaxCount;
private final int mContainer;
@@ -532,9 +540,11 @@
private int mReadCount = 0;
protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
- Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount, int container) {
+ PackageManagerHelper pmHelper, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts,
+ int maxCount, int container) {
mAppState = appState;
mUMS = ums;
+ mPmHelper = pmHelper;
mPinnedShortcuts = pinnedShortcuts;
mMaxCount = maxCount;
mContainer = container;
@@ -557,6 +567,8 @@
AppInfo info = new AppInfo(
lai,
UserCache.INSTANCE.get(mAppState.getContext()).getUserInfo(user),
+ ApiWrapper.INSTANCE.get(mAppState.getContext()),
+ mPmHelper,
mUMS.isUserQuiet(user));
info.container = mContainer;
mAppState.getIconCache().getTitleAndIcon(info, lai, false);
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 3d04cb6..a7c9652 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -32,7 +32,6 @@
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
-import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -44,15 +43,16 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.RemoteActionShortcut;
import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.util.BgObjectWithLooper;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.launcher3.views.ActivityContext;
import java.util.Arrays;
import java.util.HashMap;
@@ -61,7 +61,7 @@
/**
* Data model for digital wellbeing status of apps.
*/
-public final class WellbeingModel extends BgObjectWithLooper {
+public final class WellbeingModel implements SafeCloseable {
private static final String TAG = "WellbeingModel";
private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000};
private static final boolean DEBUG = false;
@@ -81,8 +81,12 @@
private final Context mContext;
private final String mWellbeingProviderPkg;
- private Handler mWorkerHandler;
- private ContentObserver mContentObserver;
+ private final Handler mWorkerHandler;
+ private final ContentObserver mContentObserver;
+ private final SimpleBroadcastReceiver mWellbeingAppChangeReceiver =
+ new SimpleBroadcastReceiver(t -> restartObserver());
+ private final SimpleBroadcastReceiver mAppAddRemoveReceiver =
+ new SimpleBroadcastReceiver(this::onAppPackageChanged);
private final Object mModelLock = new Object();
// Maps the action Id to the corresponding RemoteAction
@@ -94,16 +98,23 @@
private WellbeingModel(final Context context) {
mContext = context;
mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg);
- initializeInBackground("WellbeingHandler");
+ mWorkerHandler = new Handler(TextUtils.isEmpty(mWellbeingProviderPkg)
+ ? Executors.UI_HELPER_EXECUTOR.getLooper()
+ : Executors.getPackageExecutor(mWellbeingProviderPkg).getLooper());
+
+ mContentObserver = new ContentObserver(mWorkerHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateAllPackages();
+ }
+ };
+ mWorkerHandler.post(this::initializeInBackground);
}
- @Override
- protected void onInitialized(Looper looper) {
- mWorkerHandler = new Handler(looper);
- mContentObserver = newContentObserver(mWorkerHandler, this::onWellbeingUriChanged);
+ private void initializeInBackground() {
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
mContext.registerReceiver(
- new SimpleBroadcastReceiver(t -> restartObserver()),
+ mWellbeingAppChangeReceiver,
getPackageFilter(mWellbeingProviderPkg,
Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED,
Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_DATA_CLEARED,
@@ -113,17 +124,21 @@
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
- mContext.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged),
- filter, null, mWorkerHandler);
+ mContext.registerReceiver(mAppAddRemoveReceiver, filter, null, mWorkerHandler);
restartObserver();
}
}
- @WorkerThread
- private void onWellbeingUriChanged(Uri uri) {
- Preconditions.assertNonUiThread();
- updateAllPackages();
+ @Override
+ public void close() {
+ if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
+ mWorkerHandler.post(() -> {
+ mWellbeingAppChangeReceiver.unregisterReceiverSafely(mContext);
+ mAppAddRemoveReceiver.unregisterReceiverSafely(mContext);
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ });
+ }
}
public void setInTest(boolean inTest) {
@@ -147,7 +162,7 @@
@MainThread
private SystemShortcut getShortcutForApp(String packageName, int userId,
- BaseDraggingActivity activity, ItemInfo info, View originalView) {
+ Context context, ItemInfo info, View originalView) {
Preconditions.assertUIThread();
// Work profile apps are not recognized by digital wellbeing.
if (userId != UserHandle.myUserId()) {
@@ -171,7 +186,7 @@
"getShortcutForApp [" + packageName + "]: action: '" + action.getTitle()
+ "'");
}
- return new RemoteActionShortcut(action, activity, info, originalView);
+ return new RemoteActionShortcut(action, context, info, originalView);
}
}
@@ -305,9 +320,11 @@
/**
* Shortcut factory for generating wellbeing action
*/
- public static final SystemShortcut.Factory<BaseDraggingActivity> SHORTCUT_FACTORY =
- (activity, info, originalView) -> (info.getTargetComponent() == null) ? null
- : INSTANCE.get(activity).getShortcutForApp(
- info.getTargetComponent().getPackageName(), info.user.getIdentifier(),
- activity, info, originalView);
+ public static final SystemShortcut.Factory<ActivityContext> SHORTCUT_FACTORY =
+ (context, info, originalView) ->
+ (info.getTargetComponent() == null) ? null
+ : INSTANCE.get(originalView.getContext()).getShortcutForApp(
+ info.getTargetComponent().getPackageName(), info.user.getIdentifier(),
+ ActivityContext.lookupContext(originalView.getContext()),
+ info, originalView);
}
diff --git a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
new file mode 100644
index 0000000..8431396
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.picker.WidgetRecommendationCategoryProvider;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * Works with app predictor to fetch and process widget predictions displayed in a standalone
+ * widget picker activity for a UI surface.
+ */
+public class WidgetPredictionsRequester {
+ private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
+ private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
+
+ @Nullable
+ private AppPredictor mAppPredictor;
+ private final Context mContext;
+ @NonNull
+ private final String mUiSurface;
+ @NonNull
+ private final Map<PackageUserKey, List<WidgetItem>> mAllWidgets;
+
+ public WidgetPredictionsRequester(Context context, @NonNull String uiSurface,
+ @NonNull Map<PackageUserKey, List<WidgetItem>> allWidgets) {
+ mContext = context;
+ mUiSurface = uiSurface;
+ mAllWidgets = Collections.unmodifiableMap(allWidgets);
+ }
+
+ /**
+ * Requests predictions from the app predictions manager and registers the provided callback to
+ * receive updates when predictions are available.
+ *
+ * @param existingWidgets widgets that are currently added to the surface;
+ * @param callback consumer of prediction results to be called when predictions are
+ * available
+ */
+ public void request(List<AppWidgetProviderInfo> existingWidgets,
+ Consumer<List<ItemInfo>> callback) {
+ Bundle bundle = buildBundleForPredictionSession(existingWidgets, mUiSurface);
+ Predicate<WidgetItem> filter = notOnUiSurfaceFilter(existingWidgets);
+
+ MODEL_EXECUTOR.execute(() -> {
+ clear();
+ AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
+ if (apm == null) {
+ return;
+ }
+
+ mAppPredictor = apm.createAppPredictionSession(
+ new AppPredictionContext.Builder(mContext)
+ .setUiSurface(mUiSurface)
+ .setExtras(bundle)
+ .setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
+ .build());
+ mAppPredictor.registerPredictionUpdates(MODEL_EXECUTOR,
+ targets -> bindPredictions(targets, filter, callback));
+ mAppPredictor.requestPredictionUpdate();
+ });
+ }
+
+ /**
+ * Returns a bundle that can be passed in a prediction session
+ *
+ * @param addedWidgets widgets that are already added by the user in the ui surface
+ * @param uiSurface a unique identifier of the surface hosting widgets; format
+ * "widgets_xx"; note - "widgets" is reserved for home screen surface.
+ */
+ @VisibleForTesting
+ static Bundle buildBundleForPredictionSession(List<AppWidgetProviderInfo> addedWidgets,
+ String uiSurface) {
+ Bundle bundle = new Bundle();
+ ArrayList<AppTargetEvent> addedAppTargetEvents = new ArrayList<>();
+ for (AppWidgetProviderInfo info : addedWidgets) {
+ ComponentName componentName = info.provider;
+ AppTargetEvent appTargetEvent = buildAppTargetEvent(uiSurface, info, componentName);
+ addedAppTargetEvents.add(appTargetEvent);
+ }
+ bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, addedAppTargetEvents);
+ return bundle;
+ }
+
+ /**
+ * Builds the AppTargetEvent for added widgets in a form that can be passed to the widget
+ * predictor.
+ * Also see {@link PredictionHelper}
+ */
+ private static AppTargetEvent buildAppTargetEvent(String uiSurface, AppWidgetProviderInfo info,
+ ComponentName componentName) {
+ AppTargetId appTargetId = new AppTargetId("widget:" + componentName.getPackageName());
+ AppTarget appTarget = new AppTarget.Builder(appTargetId, componentName.getPackageName(),
+ /*user=*/ info.getProfile()).setClassName(componentName.getClassName()).build();
+ return new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
+ .setLaunchLocation(uiSurface).build();
+ }
+
+ /**
+ * Returns a filter to match {@link WidgetItem}s that don't exist on the UI surface.
+ */
+ @NonNull
+ @VisibleForTesting
+ static Predicate<WidgetItem> notOnUiSurfaceFilter(
+ List<AppWidgetProviderInfo> existingWidgets) {
+ Set<ComponentKey> existingComponentKeys = existingWidgets.stream().map(
+ widget -> new ComponentKey(widget.provider, widget.getProfile())).collect(
+ Collectors.toSet());
+ return widgetItem -> !existingComponentKeys.contains(widgetItem);
+ }
+
+ /** Provides the predictions returned by the predictor to the registered callback. */
+ @WorkerThread
+ private void bindPredictions(List<AppTarget> targets, Predicate<WidgetItem> filter,
+ Consumer<List<ItemInfo>> callback) {
+ List<WidgetItem> filteredPredictions = filterPredictions(targets, mAllWidgets, filter);
+ List<ItemInfo> mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions);
+
+ MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions));
+ }
+
+ /**
+ * Applies the provided filter (e.g. widgets not on workspace) on the predictions returned by
+ * the predictor.
+ */
+ @VisibleForTesting
+ static List<WidgetItem> filterPredictions(List<AppTarget> predictions,
+ Map<PackageUserKey, List<WidgetItem>> allWidgets, Predicate<WidgetItem> filter) {
+ List<WidgetItem> servicePredictedItems = new ArrayList<>();
+ List<WidgetItem> localFilteredWidgets = new ArrayList<>();
+
+ for (AppTarget prediction : predictions) {
+ List<WidgetItem> widgetsInPackage = allWidgets.get(
+ new PackageUserKey(prediction.getPackageName(), prediction.getUser()));
+ if (widgetsInPackage == null || widgetsInPackage.isEmpty()) {
+ continue;
+ }
+ String className = prediction.getClassName();
+ if (!TextUtils.isEmpty(className)) {
+ WidgetItem item = widgetsInPackage.stream()
+ .filter(w -> className.equals(w.componentName.getClassName()))
+ .filter(filter)
+ .findFirst().orElse(null);
+ if (item != null) {
+ servicePredictedItems.add(item);
+ continue;
+ }
+ }
+ // No widget was added by the service, try local filtering
+ widgetsInPackage.stream().filter(filter).findFirst()
+ .ifPresent(localFilteredWidgets::add);
+ }
+ if (servicePredictedItems.isEmpty()) {
+ servicePredictedItems.addAll(localFilteredWidgets);
+ }
+
+ return servicePredictedItems;
+ }
+
+ /**
+ * Converts the list of {@link WidgetItem}s to the list of {@link ItemInfo}s.
+ */
+ private List<ItemInfo> mapWidgetItemsToItemInfo(List<WidgetItem> widgetItems) {
+ List<ItemInfo> items;
+ if (enableCategorizedWidgetSuggestions()) {
+ WidgetRecommendationCategoryProvider categoryProvider =
+ WidgetRecommendationCategoryProvider.newInstance(mContext);
+ items = widgetItems.stream()
+ .map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
+ categoryProvider.getWidgetRecommendationCategory(mContext, it)))
+ .collect(Collectors.toList());
+ } else {
+ items = widgetItems.stream().map(it -> new PendingAddWidgetInfo(it.widgetInfo,
+ CONTAINER_WIDGETS_PREDICTION)).collect(Collectors.toList());
+ }
+ return items;
+ }
+
+ /** Cleans up any open prediction sessions. */
+ public void clear() {
+ if (mAppPredictor != null) {
+ mAppPredictor.destroy();
+ mAppPredictor = null;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index f4cbf17..64bb05e 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -19,17 +19,17 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
import android.app.prediction.AppTarget;
+import android.content.ComponentName;
import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.NonNull;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.picker.WidgetRecommendationCategoryProvider;
@@ -41,7 +41,7 @@
import java.util.stream.Collectors;
/** Task to update model as a result of predicted widgets update */
-public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask {
+public final class WidgetsPredictionUpdateTask implements ModelUpdateTask {
private final PredictorState mPredictorState;
private final List<AppTarget> mTargets;
@@ -58,47 +58,35 @@
* workspace.
*/
@Override
- public void execute(@NonNull final LauncherAppState appState,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
widget -> new ComponentKey(widget.providerName, widget.user)).collect(
Collectors.toSet());
Predicate<WidgetItem> notOnWorkspace = w -> !widgetsInWorkspace.contains(w);
- Map<PackageUserKey, List<WidgetItem>> allWidgets =
- dataModel.widgetsModel.getAllWidgetsWithoutShortcuts();
+ Map<ComponentKey, WidgetItem> allWidgets =
+ dataModel.widgetsModel.getAllWidgetComponentsWithoutShortcuts();
List<WidgetItem> servicePredictedItems = new ArrayList<>();
- List<WidgetItem> localFilteredWidgets = new ArrayList<>();
for (AppTarget app : mTargets) {
- PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(), app.getUser());
- List<WidgetItem> widgets = allWidgets.get(packageUserKey);
- if (widgets == null || widgets.isEmpty()) {
+ ComponentKey componentKey = new ComponentKey(
+ new ComponentName(app.getPackageName(), app.getClassName()), app.getUser());
+ WidgetItem widget = allWidgets.get(componentKey);
+ if (widget == null) {
continue;
}
String className = app.getClassName();
if (!TextUtils.isEmpty(className)) {
- WidgetItem item = widgets.stream()
- .filter(w -> className.equals(w.componentName.getClassName()))
- .filter(notOnWorkspace)
- .findFirst()
- .orElse(null);
- if (item != null) {
- servicePredictedItems.add(item);
- continue;
+ if (notOnWorkspace.test(widget)) {
+ servicePredictedItems.add(widget);
}
}
- // No widget was added by the service, try local filtering
- widgets.stream().filter(notOnWorkspace).findFirst()
- .ifPresent(localFilteredWidgets::add);
- }
- if (servicePredictedItems.isEmpty()) {
- servicePredictedItems.addAll(localFilteredWidgets);
}
List<ItemInfo> items;
if (enableCategorizedWidgetSuggestions()) {
- Context context = appState.getContext();
+ Context context = taskController.getApp().getContext();
WidgetRecommendationCategoryProvider categoryProvider =
WidgetRecommendationCategoryProvider.newInstance(context);
items = servicePredictedItems.stream()
@@ -115,7 +103,7 @@
new FixedContainerItems(mPredictorState.containerId, items);
dataModel.extraItems.put(mPredictorState.containerId, fixedContainerItems);
- bindExtraContainerItems(fixedContainerItems);
+ taskController.bindExtraContainerItems(fixedContainerItems);
// Don't store widgets prediction to disk because it is not used frequently.
}
diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
index 184ea71..fe9ade9 100644
--- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
+++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
@@ -25,7 +25,7 @@
/** {@link SystemShortcut.Factory} implementation to create workspace split shortcuts */
public interface QuickstepSystemShortcut {
- String TAG = QuickstepSystemShortcut.class.getSimpleName();
+ String TAG = "QuickstepSystemShortcut";
static SystemShortcut.Factory<QuickstepLauncher> getSplitSelectShortcutByPosition(
SplitPositionOption position) {
diff --git a/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt b/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt
index 2b6f77f..c94edce 100644
--- a/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt
+++ b/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt
@@ -45,7 +45,7 @@
) : SystemShortcut<T>(iconResId, labelResId, target, itemInfo, originalView) where
T : Context?,
T : ActivityContext? {
- private val TAG = SystemShortcut::class.java.simpleName
+ private val TAG = "SplitShortcut"
// Initiate splitscreen from the Home screen or Home All Apps
protected val splitSelectSource: SplitSelectSource?
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 882682d..747612d 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -182,7 +182,7 @@
}
public void dump(String prefix, PrintWriter writer) {
- writer.println(prefix + this.getClass().getSimpleName());
+ writer.println(prefix + "DepthController");
writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
writer.println(prefix + "\tmSurface=" + mSurface);
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index 4741ddd..62cc0bb 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -19,7 +19,7 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
import android.os.Debug;
import android.os.SystemProperties;
@@ -32,11 +32,14 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.DisplayController;
import com.android.quickstep.GestureState;
import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.views.DesktopAppSelectView;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Controls the visibility of the workspace and the resumed / paused state when desktop mode
* is enabled.
@@ -45,18 +48,16 @@
private static final String TAG = "DesktopVisController";
private static final boolean DEBUG = false;
- private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_stashing", false);
private final Launcher mLauncher;
+ private final Set<DesktopVisibilityListener> mDesktopVisibilityListeners = new HashSet<>();
- private int mVisibleFreeformTasksCount;
+ private int mVisibleDesktopTasksCount;
private boolean mInOverviewState;
private boolean mBackgroundStateEnabled;
private boolean mGestureInProgress;
@Nullable
private IDesktopTaskListener mDesktopTaskListener;
- private DesktopAppSelectView mSelectAppToast;
public DesktopVisibilityController(Launcher launcher) {
mLauncher = launcher;
@@ -74,28 +75,14 @@
if (DEBUG) {
Log.d(TAG, "desktop visible tasks count changed=" + visibleTasksCount);
}
- setVisibleFreeformTasksCount(visibleTasksCount);
+ setVisibleDesktopTasksCount(visibleTasksCount);
}
});
}
@Override
public void onStashedChanged(int displayId, boolean stashed) {
- if (!IS_STASHING_ENABLED) {
- return;
- }
- MAIN_EXECUTOR.execute(() -> {
- if (displayId == mLauncher.getDisplayId()) {
- if (DEBUG) {
- Log.d(TAG, "desktop stashed changed value=" + stashed);
- }
- if (stashed) {
- showSelectAppToast();
- } else {
- hideSelectAppToast();
- }
- }
- });
+ Log.w(TAG, "IDesktopTaskListener: onStashedChanged is deprecated");
}
};
SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(mDesktopTaskListener);
@@ -106,57 +93,71 @@
*/
public void unregisterSystemUiListener() {
SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(null);
+ mDesktopTaskListener = null;
}
/**
- * Whether freeform windows are visible in desktop mode.
+ * Whether desktop tasks are visible in desktop mode.
*/
- public boolean areFreeformTasksVisible() {
- boolean freeformTasksVisible = mVisibleFreeformTasksCount > 0;
+ public boolean areDesktopTasksVisible() {
+ boolean desktopTasksVisible = mVisibleDesktopTasksCount > 0;
if (DEBUG) {
- Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + freeformTasksVisible
+ Log.d(TAG, "areDesktopTasksVisible: desktopVisible=" + desktopTasksVisible
+ " overview=" + mInOverviewState);
}
- return freeformTasksVisible && !mInOverviewState;
+ return desktopTasksVisible && !mInOverviewState;
}
/**
- * Number of visible freeform windows in desktop mode.
+ * Number of visible desktop windows in desktop mode.
*/
- public int getVisibleFreeformTasksCount() {
- return mVisibleFreeformTasksCount;
+ public int getVisibleDesktopTasksCount() {
+ return mVisibleDesktopTasksCount;
+ }
+
+ /** Registers a listener for Desktop Mode visibility updates. */
+ public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) {
+ mDesktopVisibilityListeners.add(listener);
+ }
+
+ /** Removes a previously registered Desktop Mode visibility listener. */
+ public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) {
+ mDesktopVisibilityListeners.remove(listener);
}
/**
- * Sets the number of freeform windows that are visible and updates launcher visibility based on
+ * Sets the number of desktop windows that are visible and updates launcher visibility based on
* it.
*/
- public void setVisibleFreeformTasksCount(int visibleTasksCount) {
+ public void setVisibleDesktopTasksCount(int visibleTasksCount) {
if (DEBUG) {
- Log.d(TAG, "setVisibleFreeformTasksCount: visibleTasksCount=" + visibleTasksCount
- + " currentValue=" + mVisibleFreeformTasksCount);
- }
- if (!isDesktopModeSupported()) {
- return;
+ Log.d(TAG, "setVisibleDesktopTasksCount: visibleTasksCount=" + visibleTasksCount
+ + " currentValue=" + mVisibleDesktopTasksCount);
}
- if (visibleTasksCount != mVisibleFreeformTasksCount) {
- final boolean wasVisible = mVisibleFreeformTasksCount > 0;
+ if (visibleTasksCount != mVisibleDesktopTasksCount) {
+ final boolean wasVisible = mVisibleDesktopTasksCount > 0;
final boolean isVisible = visibleTasksCount > 0;
- mVisibleFreeformTasksCount = visibleTasksCount;
+ final boolean wereDesktopTasksVisibleBefore = areDesktopTasksVisible();
+ mVisibleDesktopTasksCount = visibleTasksCount;
+ final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible();
+ if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) {
+ notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow);
+ }
- if (wasVisible != isVisible) {
- if (mVisibleFreeformTasksCount > 0) {
+ if (!enableDesktopWindowingWallpaperActivity() && wasVisible != isVisible) {
+ // TODO: b/333533253 - Remove after flag rollout
+ if (mVisibleDesktopTasksCount > 0) {
setLauncherViewsVisibility(View.INVISIBLE);
if (!mInOverviewState) {
- // When freeform is visible & we're not in overview, we want launcher to
- // appear paused, this ensures that taskbar displays.
+ // When desktop tasks are visible & we're not in overview, we want launcher
+ // to appear paused, this ensures that taskbar displays.
markLauncherPaused();
}
} else {
setLauncherViewsVisibility(View.VISIBLE);
- // If freeform isn't visible ensure that launcher appears resumed to behave
- // normally.
+ // If desktop tasks aren't visible, ensure that launcher appears resumed to
+ // behave normally.
markLauncherResumed();
}
}
@@ -172,7 +173,7 @@
}
setBackgroundStateEnabled(state == BACKGROUND_APP);
// Desktop visibility tracks overview and background state separately
- setOverviewStateEnabled(state != BACKGROUND_APP && state.overviewUi);
+ setOverviewStateEnabled(state != BACKGROUND_APP && state.isRecentsViewVisible);
}
private void setOverviewStateEnabled(boolean overviewStateEnabled) {
@@ -180,38 +181,56 @@
Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled
+ " currentValue=" + mInOverviewState);
}
- if (!isDesktopModeSupported()) {
- return;
- }
if (overviewStateEnabled != mInOverviewState) {
+ final boolean wereDesktopTasksVisibleBefore = areDesktopTasksVisible();
mInOverviewState = overviewStateEnabled;
+ final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible();
+ if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) {
+ notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow);
+ }
+
+ if (enableDesktopWindowingWallpaperActivity()) {
+ return;
+ }
+ // TODO: b/333533253 - Clean up after flag rollout
+
if (mInOverviewState) {
setLauncherViewsVisibility(View.VISIBLE);
markLauncherResumed();
- } else if (areFreeformTasksVisible() && !mGestureInProgress) {
+ } else if (areDesktopTasksVisibleNow && !mGestureInProgress) {
// Switching out of overview state and gesture finished.
- // If freeform tasks are still visible, hide launcher again.
+ // If desktop tasks are still visible, hide launcher again.
setLauncherViewsVisibility(View.INVISIBLE);
markLauncherPaused();
}
}
}
+ private void notifyDesktopVisibilityListeners(boolean areDesktopTasksVisible) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyDesktopVisibilityListeners: visible=" + areDesktopTasksVisible);
+ }
+ for (DesktopVisibilityListener listener : mDesktopVisibilityListeners) {
+ listener.onDesktopVisibilityChanged(areDesktopTasksVisible);
+ }
+ DisplayController.handleInfoChangeForDesktopMode(mLauncher);
+ }
+
+ /**
+ * TODO: b/333533253 - Remove after flag rollout
+ */
private void setBackgroundStateEnabled(boolean backgroundStateEnabled) {
if (DEBUG) {
Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled
+ " currentValue=" + mBackgroundStateEnabled);
}
- if (!isDesktopModeSupported()) {
- return;
- }
if (backgroundStateEnabled != mBackgroundStateEnabled) {
mBackgroundStateEnabled = backgroundStateEnabled;
if (mBackgroundStateEnabled) {
setLauncherViewsVisibility(View.VISIBLE);
markLauncherResumed();
- } else if (areFreeformTasksVisible() && !mGestureInProgress) {
- // Switching out of background state. If freeform tasks are visible, pause launcher.
+ } else if (areDesktopTasksVisible() && !mGestureInProgress) {
+ // Switching out of background state. If desktop tasks are visible, pause launcher.
setLauncherViewsVisibility(View.INVISIBLE);
markLauncherPaused();
}
@@ -220,6 +239,8 @@
/**
* Whether recents gesture is currently in progress.
+ *
+ * TODO: b/333533253 - Remove after flag rollout
*/
public boolean isRecentsGestureInProgress() {
return mGestureInProgress;
@@ -227,11 +248,10 @@
/**
* Notify controller that recents gesture has started.
+ *
+ * TODO: b/333533253 - Remove after flag rollout
*/
public void setRecentsGestureStart() {
- if (!isDesktopModeSupported()) {
- return;
- }
if (DEBUG) {
Log.d(TAG, "setRecentsGestureStart");
}
@@ -241,11 +261,10 @@
/**
* Notify controller that recents gesture finished with the given
* {@link com.android.quickstep.GestureState.GestureEndTarget}
+ *
+ * TODO: b/333533253 - Remove after flag rollout
*/
public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) {
- if (!isDesktopModeSupported()) {
- return;
- }
if (DEBUG) {
Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget);
}
@@ -257,6 +276,9 @@
}
}
+ /**
+ * TODO: b/333533253 - Remove after flag rollout
+ */
private void setRecentsGestureInProgress(boolean gestureInProgress) {
if (gestureInProgress != mGestureInProgress) {
mGestureInProgress = gestureInProgress;
@@ -264,15 +286,12 @@
}
/**
- * Handle launcher moving to home due to home gesture or home button press.
+ * TODO: b/333533253 - Remove after flag rollout
*/
- public void onHomeActionTriggered() {
- if (IS_STASHING_ENABLED && areFreeformTasksVisible()) {
- SystemUiProxy.INSTANCE.get(mLauncher).stashDesktopApps(mLauncher.getDisplayId());
- }
- }
-
private void setLauncherViewsVisibility(int visibility) {
+ if (enableDesktopWindowingWallpaperActivity()) {
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " "
+ Debug.getCaller());
@@ -286,12 +305,18 @@
dragLayer.setVisibility(visibility);
}
if (mLauncher instanceof QuickstepLauncher ql && ql.getTaskbarUIController() != null
- && mVisibleFreeformTasksCount != 0) {
+ && mVisibleDesktopTasksCount != 0) {
ql.getTaskbarUIController().onLauncherVisibilityChanged(visibility == VISIBLE);
}
}
+ /**
+ * TODO: b/333533253 - Remove after flag rollout
+ */
private void markLauncherPaused() {
+ if (enableDesktopWindowingWallpaperActivity()) {
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "markLauncherPaused " + Debug.getCaller());
}
@@ -302,7 +327,13 @@
}
}
+ /**
+ * TODO: b/333533253 - Remove after flag rollout
+ */
private void markLauncherResumed() {
+ if (enableDesktopWindowingWallpaperActivity()) {
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "markLauncherResumed " + Debug.getCaller());
}
@@ -316,27 +347,13 @@
}
}
- private void showSelectAppToast() {
- if (mSelectAppToast != null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "show toast to select desktop apps");
- }
- Runnable onCloseCallback = () -> {
- SystemUiProxy.INSTANCE.get(mLauncher).hideStashedDesktopApps(mLauncher.getDisplayId());
- };
- mSelectAppToast = DesktopAppSelectView.show(mLauncher, onCloseCallback);
- }
-
- private void hideSelectAppToast() {
- if (mSelectAppToast == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "hide toast to select desktop apps");
- }
- mSelectAppToast.hide();
- mSelectAppToast = null;
+ /** A listener for when the user enters/exits Desktop Mode. */
+ public interface DesktopVisibilityListener {
+ /**
+ * Callback for when the user enters or exits Desktop Mode
+ *
+ * @param visible whether Desktop Mode is now visible
+ */
+ void onDesktopVisibilityChanged(boolean visible);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java
deleted file mode 100644
index 3635827..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_NOTIFICATIONS;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_QUICK_SETTINGS;
-
-import android.content.Context;
-import android.content.pm.ActivityInfo.Config;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
-
-/**
- * Controller for managing buttons and status icons in taskbar in a desktop environment.
- */
-public class DesktopNavbarButtonsViewController extends NavbarButtonsViewController {
-
- private final TaskbarActivityContext mContext;
- private final FrameLayout mNavButtonsView;
- private final ViewGroup mNavButtonContainer;
- private final ViewGroup mStartContextualContainer;
- private final View mAllAppsButton;
-
- private TaskbarControllers mControllers;
-
- public DesktopNavbarButtonsViewController(TaskbarActivityContext context,
- @Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView) {
- super(context, navigationBarPanelContext, navButtonsView);
- mContext = context;
- mNavButtonsView = navButtonsView;
- mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
- mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
- mAllAppsButton = LayoutInflater.from(context)
- .inflate(R.layout.taskbar_all_apps_button, mStartContextualContainer, false);
- mAllAppsButton.setOnClickListener(v -> mControllers.taskbarAllAppsController.toggle());
- }
-
- /**
- * Initializes the controller
- */
- @Override
- public void init(TaskbarControllers controllers) {
- mControllers = controllers;
- super.init(controllers);
- }
-
- @Override
- protected void setupController() {
- mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarHeight;
-
- // Quick settings and notifications buttons
- addButton(R.drawable.ic_sysbar_quick_settings, BUTTON_QUICK_SETTINGS,
- mNavButtonContainer, mControllers.navButtonController,
- R.id.quick_settings_button);
- addButton(R.drawable.ic_sysbar_notifications, BUTTON_NOTIFICATIONS,
- mNavButtonContainer, mControllers.navButtonController,
- R.id.notifications_button);
- // All apps button
- mStartContextualContainer.addView(mAllAppsButton);
- }
-
- /** Cleans up on destroy */
- @Override
- public void onDestroy() { }
-
- @Override
- public void onConfigurationChanged(@Config int configChanges) { }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java
deleted file mode 100644
index acfbea3..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.util.SparseArray;
-
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.quickstep.RecentsModel;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Provides recent apps functionality specifically in a desktop environment.
- */
-public class DesktopTaskbarRecentAppsController extends TaskbarRecentAppsController {
-
- private final TaskbarActivityContext mContext;
- private ArrayList<ItemInfo> mRunningApps = new ArrayList<>();
- private AppInfo[] mApps;
-
- public DesktopTaskbarRecentAppsController(TaskbarActivityContext context) {
- mContext = context;
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mApps = null;
- }
-
- @Override
- protected void setApps(AppInfo[] apps) {
- mApps = apps;
- }
-
- @Override
- protected boolean isEnabled() {
- return true;
- }
-
- /**
- * Set mRunningApps to hold currently running applications using the list of currently running
- * tasks. Filtering is also done to ignore applications that are already on the taskbar in the
- * original hotseat.
- */
- @Override
- protected void updateRunningApps(SparseArray<ItemInfo> hotseatItems) {
- ArrayList<AppInfo> runningApps = getRunningAppsFromTasks();
- ArrayList<ItemInfo> filteredRunningApps = new ArrayList<>();
- for (AppInfo runningApp : runningApps) {
- boolean shouldAddOnTaskbar = true;
- for (int i = 0; i < hotseatItems.size(); i++) {
- if (hotseatItems.keyAt(i) >= mControllers.taskbarActivityContext.getDeviceProfile()
- .numShownHotseatIcons) {
- break;
- }
- if (hotseatItems.valueAt(i).getTargetPackage()
- .equals(runningApp.getTargetPackage())) {
- shouldAddOnTaskbar = false;
- break;
- }
- }
- if (shouldAddOnTaskbar) {
- filteredRunningApps.add(new WorkspaceItemInfo(runningApp));
- }
- }
- mRunningApps = filteredRunningApps;
- mControllers.taskbarViewController.commitRunningAppsToUI();
- }
-
- /**
- * Returns a copy of hotseatItems with the addition of currently running applications.
- */
- @Override
- protected ItemInfo[] updateHotseatItemInfos(ItemInfo[] hotseatItemInfos) {
- // hotseatItemInfos.length would be 0 if deviceProfile.numShownHotseatIcons is 0, so we
- // don't want to show anything in the hotseat
- if (hotseatItemInfos.length == 0) return hotseatItemInfos;
-
- int runningAppsIndex = 0;
- ItemInfo[] newHotseatItemsInfo = Arrays.copyOf(
- hotseatItemInfos, hotseatItemInfos.length + mRunningApps.size());
- for (int i = hotseatItemInfos.length; i < newHotseatItemsInfo.length; i++) {
- newHotseatItemsInfo[i] = mRunningApps.get(runningAppsIndex);
- runningAppsIndex++;
- }
- return newHotseatItemsInfo;
- }
-
-
- /**
- * Returns a list of running applications from the list of currently running tasks.
- */
- private ArrayList<AppInfo> getRunningAppsFromTasks() {
- ArrayList<ActivityManager.RunningTaskInfo> tasks =
- RecentsModel.INSTANCE.get(mContext).getRunningTasks();
- ArrayList<AppInfo> runningApps = new ArrayList<>();
- // early return if apps is empty, since we would have no AppInfo to compare
- if (mApps == null) {
- return runningApps;
- }
-
- Set<String> seenPackages = new HashSet<>();
- for (ActivityManager.RunningTaskInfo taskInfo : tasks) {
- if (taskInfo.realActivity == null) continue;
-
- // If a different task for the same package has already been handled, skip this one
- String taskPackage = taskInfo.realActivity.getPackageName();
- if (seenPackages.contains(taskPackage)) continue;
-
- // Otherwise, get the corresponding AppInfo and add it to the list
- seenPackages.add(taskPackage);
- AppInfo app = getAppInfo(taskInfo.realActivity);
- if (app == null) continue;
- runningApps.add(app);
- }
- return runningApps;
- }
-
- /**
- * Retrieves the corresponding AppInfo for the activity.
- */
- private AppInfo getAppInfo(ComponentName activity) {
- String packageName = activity.getPackageName();
- for (AppInfo app : mApps) {
- if (!packageName.equals(app.getTargetPackage())) {
- continue;
- }
- return app;
- }
- return null;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java
deleted file mode 100644
index 2dd610c4..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.taskbar;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.quickstep.util.TISBindHelper;
-
-/**
- * A data source which integrates with a Launcher instance, used specifically for a
- * desktop environment.
- */
-public class DesktopTaskbarUIController extends TaskbarUIController {
-
- private final QuickstepLauncher mLauncher;
-
- public DesktopTaskbarUIController(QuickstepLauncher launcher) {
- mLauncher = launcher;
- }
-
- @SuppressWarnings("MissingSuperCall") // TODO: Fix me
- @Override
- protected void init(TaskbarControllers taskbarControllers) {
- super.init(taskbarControllers);
- mLauncher.getHotseat().setIconsAlpha(0f);
- mControllers.taskbarViewController.updateRunningApps();
- }
-
- @SuppressWarnings("MissingSuperCall") // TODO: Fix me
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mLauncher.getHotseat().setIconsAlpha(1f);
- }
-
- /** Disable taskbar stashing in desktop environment. */
- @Override
- public boolean supportsVisualStashing() {
- return false;
- }
-
- @Nullable
- @Override
- protected TISBindHelper getTISBindHelper() {
- return mLauncher.getTISBindHelper();
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index c0ecc61..06d9ee6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -133,4 +133,9 @@
protected TISBindHelper getTISBindHelper() {
return mRecentsActivity.getTISBindHelper();
}
+
+ @Override
+ protected String getTaskbarUIControllerName() {
+ return "FallbackTaskbarUIController";
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 8566e20..358d703 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.taskbar;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
-
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
@@ -97,7 +95,11 @@
private void openQuickSwitchView(int currentFocusedIndex) {
if (mQuickSwitchViewController != null) {
- return;
+ if (!mQuickSwitchViewController.isCloseAnimationRunning()) {
+ return;
+ }
+ // Allow the KQS to be reopened during the close animation to make it more responsive
+ closeQuickSwitchView(false);
}
TaskbarOverlayContext overlayContext =
mControllers.taskbarOverlayController.requestWindow();
@@ -113,9 +115,7 @@
DesktopVisibilityController desktopController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
final boolean onDesktop =
- isDesktopModeSupported()
- && desktopController != null
- && desktopController.areFreeformTasksVisible();
+ desktopController != null && desktopController.areDesktopTasksVisible();
if (mModel.isTaskListValid(mTaskListChangeId)) {
// When we are opening the KQS with no focus override, check if the first task is
@@ -148,28 +148,16 @@
});
}
- private void processLoadedTasks(ArrayList<GroupTask> tasks) {
+ private void processLoadedTasks(List<GroupTask> tasks) {
// Only store MAX_TASK tasks, from most to least recent
Collections.reverse(tasks);
-
- // Hide all desktop tasks and show them on the hidden tile
- int hiddenDesktopTasks = 0;
- if (isDesktopModeSupported()) {
- DesktopTask desktopTask = findDesktopTask(tasks);
- if (desktopTask != null) {
- hiddenDesktopTasks = desktopTask.tasks.size();
- tasks = tasks.stream()
- .filter(t -> !(t instanceof DesktopTask))
- .collect(Collectors.toCollection(ArrayList<GroupTask>::new));
- }
- }
mTasks = tasks.stream()
.limit(MAX_TASKS)
.collect(Collectors.toList());
- mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS) + hiddenDesktopTasks;
+ mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS);
}
- private void processLoadedTasksOnDesktop(ArrayList<GroupTask> tasks) {
+ private void processLoadedTasksOnDesktop(List<GroupTask> tasks) {
// Find the single desktop task that contains a grouping of desktop tasks
DesktopTask desktopTask = findDesktopTask(tasks);
@@ -185,7 +173,7 @@
}
@Nullable
- private DesktopTask findDesktopTask(ArrayList<GroupTask> tasks) {
+ private DesktopTask findDesktopTask(List<GroupTask> tasks) {
return (DesktopTask) tasks.stream()
.filter(t -> t instanceof DesktopTask)
.findFirst()
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index 5b407f0..39b4f77 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -23,6 +23,7 @@
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
@@ -174,21 +175,23 @@
return;
}
if (updateFunction == null) {
- applyThumbnail(thumbnailView, task.thumbnail);
+ applyThumbnail(thumbnailView, task.colorBackground, task.thumbnail);
return;
}
- updateFunction.updateThumbnailInBackground(
- task, thumbnailData -> applyThumbnail(thumbnailView, thumbnailData));
+ updateFunction.updateThumbnailInBackground(task, thumbnailData ->
+ applyThumbnail(thumbnailView, task.colorBackground, thumbnailData));
}
private void applyThumbnail(
@NonNull ImageView thumbnailView,
- ThumbnailData thumbnailData) {
- Bitmap bm = thumbnailData == null ? null : thumbnailData.thumbnail;
+ @ColorInt int backgroundColor,
+ @Nullable ThumbnailData thumbnailData) {
+ Bitmap bm = thumbnailData == null ? null : thumbnailData.getThumbnail();
if (thumbnailView.getVisibility() != VISIBLE) {
thumbnailView.setVisibility(VISIBLE);
}
+ thumbnailView.getBackground().setTint(bm == null ? backgroundColor : Color.TRANSPARENT);
thumbnailView.setImageDrawable(new BlurredBitmapDrawable(bm, THUMBNAIL_BLUR_RADIUS));
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 2d9e236..5d47212 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -36,11 +36,14 @@
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.core.content.res.ResourcesCompat;
import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
@@ -48,6 +51,7 @@
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import java.util.HashMap;
@@ -92,6 +96,7 @@
private HorizontalScrollView mScrollView;
private ConstraintLayout mContent;
+ private int mTaskViewWidth;
private int mTaskViewHeight;
private int mSpacing;
private int mOutlineRadius;
@@ -128,6 +133,8 @@
mContent = findViewById(R.id.content);
Resources resources = getResources();
+ mTaskViewWidth = resources.getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_taskview_width);
mTaskViewHeight = resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_taskview_height);
mSpacing = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_spacing);
@@ -135,21 +142,18 @@
mIsRtl = Utilities.isRtl(resources);
}
- @NonNull
private KeyboardQuickSwitchTaskView createAndAddTaskView(
int index,
- int width,
boolean isFinalView,
- boolean updateTasks,
+ @LayoutRes int resId,
@NonNull LayoutInflater layoutInflater,
- @Nullable View previousView,
- @NonNull List<GroupTask> groupTasks) {
+ @Nullable View previousView) {
KeyboardQuickSwitchTaskView taskView = (KeyboardQuickSwitchTaskView) layoutInflater.inflate(
- R.layout.keyboard_quick_switch_taskview, mContent, false);
+ resId, mContent, false);
taskView.setId(View.generateViewId());
taskView.setOnClickListener(v -> mViewCallbacks.launchTaskAt(index));
- LayoutParams lp = new LayoutParams(width, mTaskViewHeight);
+ LayoutParams lp = new LayoutParams(mTaskViewWidth, mTaskViewHeight);
// Create a left-to-right ordering of views (or right-to-left in RTL locales)
if (previousView != null) {
lp.startToEnd = previousView.getId();
@@ -167,45 +171,11 @@
lp.horizontalBias = 1f;
}
- GroupTask groupTask = groupTasks.get(index);
- taskView.setThumbnails(
- groupTask.task1,
- groupTask.task2,
- updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
- updateTasks ? mViewCallbacks::updateIconInBackground : null);
-
mContent.addView(taskView, lp);
+
return taskView;
}
- private void createAndAddOverviewButton(
- int width,
- @NonNull LayoutInflater layoutInflater,
- @Nullable View previousView,
- @NonNull String overflowString) {
- KeyboardQuickSwitchTaskView overviewButton =
- (KeyboardQuickSwitchTaskView) layoutInflater.inflate(
- R.layout.keyboard_quick_switch_overview, this, false);
- overviewButton.setOnClickListener(v -> mViewCallbacks.launchTaskAt(MAX_TASKS));
-
- overviewButton.<TextView>findViewById(R.id.text).setText(overflowString);
-
- ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
- width, mTaskViewHeight);
- if (previousView == null) {
- lp.startToStart = PARENT_ID;
- } else {
- lp.endToEnd = PARENT_ID;
- lp.startToEnd = previousView.getId();
- }
- lp.topToTop = PARENT_ID;
- lp.bottomToBottom = PARENT_ID;
- lp.setMarginEnd(mSpacing);
- lp.setMarginStart(mSpacing);
-
- mContent.addView(overviewButton, lp);
- }
-
protected void applyLoadPlan(
@NonNull Context context,
@NonNull List<GroupTask> groupTasks,
@@ -215,32 +185,57 @@
@NonNull KeyboardQuickSwitchViewController.ViewCallbacks viewCallbacks) {
mViewCallbacks = viewCallbacks;
Resources resources = context.getResources();
- int width = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_taskview_width);
- View previousView = null;
+ Resources.Theme theme = context.getTheme();
+ View previousTaskView = null;
LayoutInflater layoutInflater = LayoutInflater.from(context);
int tasksToDisplay = Math.min(MAX_TASKS, groupTasks.size());
for (int i = 0; i < tasksToDisplay; i++) {
- previousView = createAndAddTaskView(
+ GroupTask groupTask = groupTasks.get(i);
+ KeyboardQuickSwitchTaskView currentTaskView = createAndAddTaskView(
i,
- width,
/* isFinalView= */ i == tasksToDisplay - 1 && numHiddenTasks == 0,
- updateTasks,
+ groupTask instanceof DesktopTask
+ ? R.layout.keyboard_quick_switch_textonly_taskview
+ : R.layout.keyboard_quick_switch_taskview,
layoutInflater,
- previousView,
- groupTasks);
+ previousTaskView);
+
+ if (groupTask instanceof DesktopTask desktopTask) {
+ HashMap<String, Integer> args = new HashMap<>();
+ args.put("count", desktopTask.tasks.size());
+
+ currentTaskView.<ImageView>findViewById(R.id.icon).setImageDrawable(
+ ResourcesCompat.getDrawable(resources, R.drawable.ic_desktop, theme));
+ currentTaskView.<TextView>findViewById(R.id.text).setText(new MessageFormat(
+ resources.getString(R.string.quick_switch_desktop),
+ Locale.getDefault()).format(args));
+ } else {
+ currentTaskView.setThumbnails(
+ groupTask.task1,
+ groupTask.task2,
+ updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
+ updateTasks ? mViewCallbacks::updateIconInBackground : null);
+ }
+ previousTaskView = currentTaskView;
}
if (numHiddenTasks > 0) {
HashMap<String, Integer> args = new HashMap<>();
args.put("count", numHiddenTasks);
- createAndAddOverviewButton(
- width,
+
+ View overviewButton = createAndAddTaskView(
+ MAX_TASKS,
+ /* isFinalView= */ true,
+ R.layout.keyboard_quick_switch_textonly_taskview,
layoutInflater,
- previousView,
- new MessageFormat(
- resources.getString(R.string.quick_switch_overflow),
- Locale.getDefault()).format(args));
+ previousTaskView);
+
+ overviewButton.<ImageView>findViewById(R.id.icon).setImageDrawable(
+ ResourcesCompat.getDrawable(resources, R.drawable.view_carousel, theme));
+ overviewButton.<TextView>findViewById(R.id.text).setText(new MessageFormat(
+ resources.getString(R.string.quick_switch_overflow),
+ Locale.getDefault()).format(args));
}
mDisplayingRecentTasks = !groupTasks.isEmpty();
@@ -362,8 +357,9 @@
OPEN_OUTLINE_INTERPOLATOR));
}
});
- animateFocusMove(-1, currentFocusIndexOverride == -1
- ? Math.min(mContent.getChildCount(), 1) : currentFocusIndexOverride);
+ animateFocusMove(-1, Math.min(
+ mContent.getChildCount() - 1,
+ currentFocusIndexOverride == -1 ? 1 : currentFocusIndexOverride));
displayedContent.setVisibility(VISIBLE);
setVisibility(VISIBLE);
requestFocus();
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 701edd0..d6ee92f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -32,6 +32,7 @@
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.SlideInRemoteTransition;
import com.android.systemui.shared.recents.model.Task;
@@ -94,8 +95,12 @@
mViewCallbacks);
}
+ boolean isCloseAnimationRunning() {
+ return mCloseAnimation != null;
+ }
+
protected void closeQuickSwitchView(boolean animate) {
- if (mCloseAnimation != null) {
+ if (isCloseAnimationRunning()) {
// Let currently-running animation finish.
if (!animate) {
mCloseAnimation.end();
@@ -130,7 +135,7 @@
}
private int launchTaskAt(int index) {
- if (mCloseAnimation != null) {
+ if (isCloseAnimationRunning()) {
// Ignore taps on task views and alt key unpresses while the close animation is running.
return -1;
}
@@ -138,7 +143,7 @@
// views have been added in the KeyboardQuickSwitchView.
GroupTask task = mControllerCallbacks.getTaskAt(index);
if (task == null) {
- return Math.max(0, index);
+ return mOnDesktop ? 1 : Math.max(0, index);
}
if (mControllerCallbacks.isTaskRunning(task)) {
// Ignore attempts to run the selected task if it is already running.
@@ -153,7 +158,13 @@
AnimationUtils.loadInterpolator(
context, android.R.interpolator.fast_out_extra_slow_in)),
"SlideInTransition");
- if (mOnDesktop) {
+ if (task instanceof DesktopTask) {
+ UI_HELPER_EXECUTOR.execute(() ->
+ SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
+ .showDesktopApps(
+ mKeyboardQuickSwitchView.getDisplay().getDisplayId(),
+ remoteTransition));
+ } else if (mOnDesktop) {
UI_HELPER_EXECUTOR.execute(() ->
SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
.showDesktopApp(task.task1.key.id));
@@ -186,7 +197,7 @@
pw.println(prefix + "KeyboardQuickSwitchViewController:");
pw.println(prefix + "\thasFocus=" + mKeyboardQuickSwitchView.hasFocus());
- pw.println(prefix + "\tcloseAnimationRunning=" + (mCloseAnimation != null));
+ pw.println(prefix + "\tisCloseAnimationRunning=" + isCloseAnimationRunning());
pw.println(prefix + "\tmCurrentFocusIndex=" + mCurrentFocusIndex);
}
@@ -239,8 +250,7 @@
}
void launchTaskAt(int index) {
- mCurrentFocusIndex = Utilities.boundToRange(
- index, 0, mKeyboardQuickSwitchView.getChildCount() - 1);
+ mCurrentFocusIndex = index;
mControllers.taskbarActivityContext.launchKeyboardFocusedTask();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 30be058..779009a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -16,28 +16,23 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
-import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
import android.animation.Animator;
import android.animation.AnimatorSet;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.TaskTransitionSpec;
-import android.view.WindowManagerGlobal;
import android.window.RemoteTransition;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.logging.InstanceId;
@@ -49,11 +44,14 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.OnboardingPrefs;
+import com.android.quickstep.HomeVisibilityState;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -79,6 +77,7 @@
AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max);
private final QuickstepLauncher mLauncher;
+ private final HomeVisibilityState mHomeState;
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
dp -> {
@@ -87,6 +86,8 @@
mControllers.taskbarViewController.onRotationChanged(dp);
}
};
+ private final HomeVisibilityState.VisibilityChangeListener mVisibilityChangeListener =
+ this::onLauncherVisibilityChanged;
// Initialized in init.
private final TaskbarLauncherStateController
@@ -94,6 +95,7 @@
public LauncherTaskbarUIController(QuickstepLauncher launcher) {
mLauncher = launcher;
+ mHomeState = SystemUiProxy.INSTANCE.get(mLauncher).getHomeVisibilityState();
}
@Override
@@ -104,8 +106,11 @@
mControllers.getSharedState().sysuiStateFlags);
mLauncher.setTaskbarUIController(this);
-
- onLauncherVisibilityChanged(mLauncher.hasBeenResumed(), true /* fromInit */);
+ mHomeState.addListener(mVisibilityChangeListener);
+ onLauncherVisibilityChanged(
+ Flags.useActivityOverlay()
+ ? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed(),
+ true /* fromInit */);
onStashedInAppChanged(mLauncher.getDeviceProfile());
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
@@ -129,7 +134,7 @@
mLauncher.setTaskbarUIController(null);
mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
- updateTaskTransitionSpec(true);
+ mHomeState.removeListener(mVisibilityChangeListener);
}
private void onInAppDisplayProgressChanged() {
@@ -191,7 +196,7 @@
? TRANSIENT_TASKBAR_TRANSITION_DURATION
: (!isVisible
? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
- : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION));
+ : QuickstepTransitionManager.getTaskbarToHomeDuration()));
}
@Nullable
@@ -208,20 +213,15 @@
DesktopVisibilityController desktopController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
- final boolean onDesktop =
- isDesktopModeSupported()
- && desktopController != null
- && desktopController.areFreeformTasksVisible();
- if (onDesktop) {
+ if (!enableDesktopWindowingWallpaperActivity()
+ && desktopController != null
+ && desktopController.areDesktopTasksVisible()) {
+ // TODO: b/333533253 - Remove after flag rollout
isVisible = false;
}
mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, isVisible);
- // TODO(b/308851855): Skip animation for launching split from home, will refine later
- boolean skipAnimForSplit = enableSplitContextually() &&
- mLauncher.areBothSplitAppsConfirmed() &&
- mLauncher.getStateManager().getState() == LauncherState.NORMAL;
- if (skipAnimForSplit || fromInit) {
+ if (fromInit) {
duration = 0;
}
return mTaskbarLauncherStateController.applyState(duration, startAnimation);
@@ -234,7 +234,8 @@
@Override
public void refreshResumedState() {
- onLauncherVisibilityChanged(mLauncher.hasBeenResumed());
+ onLauncherVisibilityChanged(Flags.useActivityOverlay()
+ ? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed());
}
@Override
@@ -272,26 +273,6 @@
private void onStashedInAppChanged(DeviceProfile deviceProfile) {
boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp();
deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps;
- updateTaskTransitionSpec(taskbarStashedInApps);
- }
-
- private void updateTaskTransitionSpec(boolean taskbarIsHidden) {
- try {
- if (taskbarIsHidden) {
- // Clear custom task transition settings when the taskbar is stashed
- WindowManagerGlobal.getWindowManagerService().clearTaskTransitionSpec();
- } else {
- // Adjust task transition spec to account for taskbar being visible
- WindowManagerGlobal.getWindowManagerService().setTaskTransitionSpec(
- new TaskTransitionSpec(
- mLauncher.getColor(R.color.taskbar_background)));
- }
- } catch (RemoteException e) {
- // This shouldn't happen but if it does task animations won't look good until the
- // taskbar stashing state is changed.
- Log.e(TAG, "Failed to update task transition spec to account for new taskbar state",
- e);
- }
}
/**
@@ -299,6 +280,8 @@
*/
public void showEduOnAppLaunch() {
if (!shouldShowEduOnAppLaunch()) {
+ // Called in case the edu finishes and search edu is still pending
+ mControllers.taskbarEduTooltipController.maybeShowSearchEdu();
return;
}
@@ -312,6 +295,11 @@
mControllers.taskbarEduTooltipController.maybeShowSwipeEdu();
}
+ /** Will make the next onRecentsAnimationFinished() animation a no-op. */
+ public void setSkipNextRecentsAnimEnd() {
+ mTaskbarLauncherStateController.setSkipNextRecentsAnimEnd();
+ }
+
/**
* Returns {@code true} if a Taskbar education should be shown on application launch.
*/
@@ -392,7 +380,7 @@
}
@Override
- public void updateStateForSysuiFlags(int sysuiFlags) {
+ public void updateStateForSysuiFlags(@SystemUiStateFlags long sysuiFlags) {
mTaskbarLauncherStateController.updateStateForSysuiFlags(sysuiFlags);
}
@@ -409,15 +397,15 @@
}
@Override
- protected boolean isInOverview() {
- return mTaskbarLauncherStateController.isInOverview();
+ protected boolean isInOverviewUi() {
+ return mTaskbarLauncherStateController.isInOverviewUi();
}
@Override
protected boolean canToggleHomeAllApps() {
return mLauncher.isResumed()
- && !mTaskbarLauncherStateController.isInOverview()
- && !mLauncher.areFreeformTasksVisible();
+ && !mTaskbarLauncherStateController.isInOverviewUi()
+ && !mLauncher.areDesktopTasksVisible();
}
@Override
@@ -459,4 +447,9 @@
mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw);
}
+
+ @Override
+ protected String getTaskbarUIControllerName() {
+ return "LauncherTaskbarUIController";
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 2f11fd7..81581b8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -45,6 +45,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import android.animation.ArgbEvaluator;
@@ -105,6 +106,7 @@
import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -133,6 +135,7 @@
private static final int FLAG_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 12;
private static final int FLAG_SMALL_SCREEN = 1 << 13;
private static final int FLAG_SLIDE_IN_VIEW_VISIBLE = 1 << 14;
+ private static final int FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING = 1 << 15;
/**
* Flags where a UI could be over Taskbar surfaces, so the color override should be disabled.
@@ -142,6 +145,9 @@
private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
+ private static final double SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95;
+ private static final double SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05;
+
public static final int ALPHA_INDEX_IMMERSIVE_MODE = 0;
public static final int ALPHA_INDEX_KEYGUARD_OR_DISABLE = 1;
public static final int ALPHA_INDEX_SUW = 2;
@@ -194,7 +200,8 @@
private TaskbarControllers mControllers;
private boolean mIsImeRenderingNavButtons;
private ImageView mA11yButton;
- private int mSysuiStateFlags;
+ @SystemUiStateFlags
+ private long mSysuiStateFlags;
private ImageView mBackButton;
private ImageView mHomeButton;
private MultiValueAlpha mBackButtonAlpha;
@@ -281,8 +288,9 @@
// - Notification shade is expanded
// - IME is showing (add separate translation for IME)
// - VoiceInteractionWindow (assistant) is showing
+ // - Keyboard shortcuts helper is showing
int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
- | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
+ | FLAG_VOICE_INTERACTION_WINDOW_SHOWING | FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING;
mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
1, 0));
@@ -314,38 +322,28 @@
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
-
- // Rotation button
- RotationButton rotationButton = new RotationButtonImpl(
- addButton(mEndContextualContainer, R.id.rotate_suggestion,
- R.layout.taskbar_contextual_button));
- rotationButton.hide();
- mControllers.rotationButtonController.setRotationButton(rotationButton, null);
- } else {
- mFloatingRotationButton = new FloatingRotationButton(
- ENABLE_TASKBAR_NAVBAR_UNIFICATION ? mNavigationBarPanelContext : mContext,
- R.string.accessibility_rotate_button,
- R.layout.rotate_suggestion,
- R.id.rotate_suggestion,
- R.dimen.floating_rotation_button_min_margin,
- R.dimen.rounded_corner_content_padding,
- R.dimen.floating_rotation_button_taskbar_left_margin,
- R.dimen.floating_rotation_button_taskbar_bottom_margin,
- R.dimen.floating_rotation_button_diameter,
- R.dimen.key_button_ripple_max_width,
- R.bool.floating_rotation_button_position_left);
- mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
- mRotationButtonListener);
-
- if (!mIsImeRenderingNavButtons) {
- View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
- mStartContextualContainer, mControllers.navButtonController, R.id.back);
- imeDownButton.setRotation(Utilities.isRtl(resources) ? 90 : -90);
- // Only show when IME is visible.
- mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
- flags -> (flags & FLAG_IME_VISIBLE) != 0));
- }
+ } else if (!mIsImeRenderingNavButtons) {
+ View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
+ mStartContextualContainer, mControllers.navButtonController, R.id.back);
+ imeDownButton.setRotation(Utilities.isRtl(resources) ? 90 : -90);
+ // Only show when IME is visible.
+ mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
+ flags -> (flags & FLAG_IME_VISIBLE) != 0));
}
+ mFloatingRotationButton = new FloatingRotationButton(
+ ENABLE_TASKBAR_NAVBAR_UNIFICATION ? mNavigationBarPanelContext : mContext,
+ R.string.accessibility_rotate_button,
+ R.layout.rotate_suggestion,
+ R.id.rotate_suggestion,
+ R.dimen.floating_rotation_button_min_margin,
+ R.dimen.rounded_corner_content_padding,
+ R.dimen.floating_rotation_button_taskbar_left_margin,
+ R.dimen.floating_rotation_button_taskbar_bottom_margin,
+ R.dimen.floating_rotation_button_diameter,
+ R.dimen.key_button_ripple_max_width,
+ R.bool.floating_rotation_button_position_left);
+ mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
+ mRotationButtonListener);
applyState();
mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
@@ -383,8 +381,15 @@
(flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
(flags & FLAG_KEYGUARD_OCCLUDED) != 0;
return (flags & FLAG_DISABLE_BACK) == 0
+ && (!mContext.isGestureNav() || !mContext.isUserSetupComplete())
&& ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
}));
+ // Hide back button in SUW if keyboard is showing (IME draws its own back).
+ if (mIsImeRenderingNavButtons) {
+ mPropertyHolders.add(new StatePropertyHolder(
+ mBackButtonAlpha.get(ALPHA_INDEX_SUW),
+ flags -> (flags & FLAG_IME_VISIBLE) == 0));
+ }
mPropertyHolders.add(new StatePropertyHolder(mBackButton,
flags -> (flags & FLAG_IME_VISIBLE) != 0,
ROTATION_DRAWABLE_PERCENT, 1f, 0f));
@@ -407,8 +412,8 @@
mPropertyHolders.add(
new StatePropertyHolder(mHomeButtonAlpha.get(
ALPHA_INDEX_KEYGUARD_OR_DISABLE),
- flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
- (flags & FLAG_DISABLE_HOME) == 0));
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
+ && (flags & FLAG_DISABLE_HOME) == 0 && !mContext.isGestureNav()));
// Recents button
mRecentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
@@ -426,7 +431,7 @@
});
mPropertyHolders.add(new StatePropertyHolder(mRecentsButton,
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
- && !mContext.isNavBarKidsModeActive()));
+ && !mContext.isNavBarKidsModeActive() && !mContext.isGestureNav()));
// A11y button
mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
@@ -442,7 +447,7 @@
navButtonController.onButtonLongClick(BUTTON_SPACE, view));
}
- private void parseSystemUiFlags(int sysUiStateFlags) {
+ 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;
@@ -450,12 +455,14 @@
boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
boolean isBackDisabled = (sysUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
- int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+ long shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
boolean isVoiceInteractionWindowShowing =
(sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0;
+ 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_VISIBLE, isImeVisible);
@@ -467,6 +474,7 @@
updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
updateStateForFlag(FLAG_VOICE_INTERACTION_WINDOW_SHOWING, isVoiceInteractionWindowShowing);
+ updateStateForFlag(FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING, isKeyboardShortcutHelperShowing);
if (mA11yButton != null) {
// Only used in 3 button
@@ -477,7 +485,8 @@
}
}
- public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
+ public void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags,
+ boolean skipAnim) {
if (systemUiStateFlags == mSysuiStateFlags) {
return;
}
@@ -735,18 +744,39 @@
// end-aligned, so start-align instead.
FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
mNavButtonContainer.getLayoutParams();
+ FrameLayout.LayoutParams navButtonsViewLayoutParams = (FrameLayout.LayoutParams)
+ mNavButtonsView.getLayoutParams();
Resources resources = mContext.getResources();
DeviceProfile deviceProfile = mContext.getDeviceProfile();
- int setupMargin = resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin);
- navButtonsLayoutParams.setMarginStart(setupMargin);
- navButtonsLayoutParams.bottomMargin = !deviceProfile.isLandscape
- ? 0
- : setupMargin -
- (resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2);
+
navButtonsLayoutParams.setMarginEnd(0);
navButtonsLayoutParams.gravity = Gravity.START;
- mNavButtonsView.getLayoutParams().height =
- mControllers.taskbarActivityContext.getSetupWindowSize();
+ mControllers.taskbarActivityContext.setTaskbarWindowSize(
+ mControllers.taskbarActivityContext.getSetupWindowSize());
+
+ // If SUW is on a large screen device that is landscape (or has a square aspect
+ // ratio) the back button has to be placed accordingly
+ if ((deviceProfile.isTablet && deviceProfile.isLandscape)
+ || (deviceProfile.aspectRatio > SQUARE_ASPECT_RATIO_BOTTOM_BOUND
+ && deviceProfile.aspectRatio < SQUARE_ASPECT_RATIO_UPPER_BOUND)) {
+ navButtonsLayoutParams.setMarginStart(
+ resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin));
+ navButtonsViewLayoutParams.bottomMargin = resources.getDimensionPixelSize(
+ R.dimen.taskbar_back_button_suw_bottom_margin);
+ navButtonsLayoutParams.height = resources.getDimensionPixelSize(
+ R.dimen.taskbar_back_button_suw_height);
+ } else {
+ int phoneOrPortraitSetupMargin = resources.getDimensionPixelSize(
+ R.dimen.taskbar_contextual_button_suw_margin);
+ navButtonsLayoutParams.setMarginStart(phoneOrPortraitSetupMargin);
+ navButtonsLayoutParams.bottomMargin = !deviceProfile.isLandscape
+ ? 0
+ : phoneOrPortraitSetupMargin - (resources.getDimensionPixelSize(
+ R.dimen.taskbar_nav_buttons_size) / 2);
+ navButtonsViewLayoutParams.height = resources.getDimensionPixelSize(
+ R.dimen.taskbar_contextual_button_suw_height);
+ }
+ mNavButtonsView.setLayoutParams(navButtonsViewLayoutParams);
mNavButtonContainer.setLayoutParams(navButtonsLayoutParams);
}
@@ -767,7 +797,6 @@
NavButtonLayoutter navButtonLayoutter =
NavButtonLayoutFactory.Companion.getUiLayoutter(
dp, mNavButtonsView, mImeSwitcherButton,
- mControllers.rotationButtonController.getRotationButton(),
mA11yButton, mSpace, res, isInKidsMode, isInSetup, isThreeButtonNav,
mContext.isPhoneMode(), mWindowManagerProxy.getRotation(mContext));
navButtonLayoutter.layoutButtons(mContext, isA11yButtonPersistent());
@@ -778,13 +807,6 @@
if (isInSetup) {
handleSetupUi();
-
- // Hide back button in SUW if keyboard is showing (IME draws its own back).
- if (mIsImeRenderingNavButtons) {
- mPropertyHolders.add(new StatePropertyHolder(
- mBackButtonAlpha.get(ALPHA_INDEX_SUW),
- flags -> (flags & FLAG_IME_VISIBLE) == 0));
- }
} else if (isInKidsMode) {
int iconSize = res.getDimensionPixelSize(
R.dimen.taskbar_icon_size_kids);
@@ -1040,6 +1062,8 @@
appendFlag(str, flags, FLAG_SCREEN_PINNING_ACTIVE, "FLAG_SCREEN_PINNING_ACTIVE");
appendFlag(str, flags, FLAG_VOICE_INTERACTION_WINDOW_SHOWING,
"FLAG_VOICE_INTERACTION_WINDOW_SHOWING");
+ appendFlag(str, flags, FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING,
+ "FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING");
return str.toString();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
index 83e4571..94e2244 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
@@ -15,9 +15,12 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
@@ -111,4 +114,17 @@
setBackgroundColor(newColor);
}
}
+
+ /**
+ * Updates the handle scale.
+ *
+ * @param scale target scale to animate towards (starting from current scale)
+ * @param durationMs milliseconds for the animation to take
+ */
+ public void animateScale(float scale, long durationMs) {
+ ObjectAnimator scaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+ PropertyValuesHolder.ofFloat(SCALE_PROPERTY, scale));
+ scaleAnim.setDuration(durationMs).setAutoCancel(true);
+ scaleAnim.start();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index f258b47..252f2a8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -42,6 +42,7 @@
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.NavHandle;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.PrintWriter;
@@ -57,6 +58,10 @@
public static final int ALPHA_INDEX_HIDDEN_WHILE_DREAMING = 3;
private static final int NUM_ALPHA_CHANNELS = 4;
+ // Values for long press animations, picked to most closely match navbar spec.
+ private static final float SCALE_TOUCH_ANIMATION_SHRINK = 0.85f;
+ private static final float SCALE_TOUCH_ANIMATION_EXPAND = 1.18f;
+
/**
* The SharedPreferences key for whether the stashed handle region is dark.
*/
@@ -113,7 +118,7 @@
mControllers = controllers;
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Resources resources = mActivity.getResources();
- if (mActivity.isPhoneGestureNavMode()) {
+ if (mActivity.isPhoneGestureNavMode() || mActivity.isTinyTaskbar()) {
mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_phone_size);
mStashedHandleWidth =
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
@@ -299,7 +304,7 @@
homeDisabled ? 0 : 1);
}
- public void updateStateForSysuiFlags(int systemUiStateFlags) {
+ public void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags) {
mTaskbarHidden = (systemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
updateRegionSamplingWindowVisibility();
}
@@ -324,7 +329,13 @@
@Override
public void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) {
- // TODO(b/308693847): Animate similarly to NavigationHandle.java (SysUI).
+ float targetScale;
+ if (isTouchDown) {
+ targetScale = shrink ? SCALE_TOUCH_ANIMATION_SHRINK : SCALE_TOUCH_ANIMATION_EXPAND;
+ } else {
+ targetScale = 1f;
+ }
+ mStashedHandleView.animateScale(targetScale, durationMs);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index cb8cd37..6b62c86 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -15,8 +15,8 @@
*/
package com.android.launcher3.taskbar;
-import static android.content.pm.PackageManager.FEATURE_PC;
import static android.os.Trace.TRACE_TAG_APP;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -36,14 +36,14 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
-import static com.android.launcher3.taskbar.TaskbarDragLayerController.TASKBAR_REAPPEAR_DELAY_MS;
import static com.android.launcher3.testing.shared.ResourceUtils.getBoolByName;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
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.wm.shell.Flags.enableTinyTaskbar;
import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
@@ -62,7 +62,6 @@
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
-import android.view.RoundedCorner;
import android.view.Surface;
import android.view.View;
import android.view.WindowInsets;
@@ -81,7 +80,6 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.config.FeatureFlags;
@@ -91,6 +89,7 @@
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo;
+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.WorkspaceItemInfo;
@@ -100,11 +99,13 @@
import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
+import com.android.launcher3.taskbar.bubbles.BubbleBarPinController;
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.taskbar.bubbles.BubbleDismissController;
import com.android.launcher3.taskbar.bubbles.BubbleDragController;
+import com.android.launcher3.taskbar.bubbles.BubblePinController;
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
@@ -113,8 +114,8 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
-import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors;
@@ -127,12 +128,15 @@
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.ViewCache;
import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.NavHandle;
+import com.android.quickstep.RecentsModel;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.unfold.updates.RotationChangeProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
@@ -161,7 +165,6 @@
private final TaskbarControllers mControllers;
private final WindowManager mWindowManager;
- private final @Nullable RoundedCorner mLeftCorner, mRightCorner;
private DeviceProfile mDeviceProfile;
private WindowManager.LayoutParams mWindowLayoutParams;
private boolean mIsFullscreen;
@@ -222,16 +225,8 @@
Context c = getApplicationContext();
mWindowManager = c.getSystemService(WindowManager.class);
- boolean phoneMode = isPhoneMode();
- mLeftCorner = phoneMode
- ? null
- : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
- mRightCorner = phoneMode
- ? null
- : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
-
// Inflate views.
- int taskbarLayout = DisplayController.isTransientTaskbar(this) && !phoneMode
+ int taskbarLayout = DisplayController.isTransientTaskbar(this) && !isPhoneMode()
? R.layout.transient_taskbar
: R.layout.taskbar;
mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(taskbarLayout, null, false);
@@ -244,8 +239,6 @@
mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);
- final boolean isDesktopMode = getPackageManager().hasSystemFeature(FEATURE_PC);
-
// If Bubble bar is present, TaskbarControllers depends on it so build it first.
Optional<BubbleControllers> bubbleControllersOptional = Optional.empty();
BubbleBarController.onTaskbarRecreated();
@@ -256,7 +249,12 @@
new BubbleStashController(this),
new BubbleStashedHandleViewController(this, bubbleHandleView),
new BubbleDragController(this),
- new BubbleDismissController(this, mDragLayer)));
+ new BubbleDismissController(this, mDragLayer),
+ new BubbleBarPinController(this, mDragLayer,
+ () -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
+ new BubblePinController(this, mDragLayer,
+ () -> DisplayController.INSTANCE.get(this).getInfo().currentSize)
+ ));
}
// Construct controllers.
@@ -268,16 +266,12 @@
R.drawable.ic_sysbar_rotate_button_cw_start_0,
R.drawable.ic_sysbar_rotate_button_cw_start_90,
() -> getDisplay().getRotation());
- rotationButtonController.setBgExecutor(Executors.THREAD_POOL_EXECUTOR);
+ rotationButtonController.setBgExecutor(Executors.UI_HELPER_EXECUTOR);
mControllers = new TaskbarControllers(this,
new TaskbarDragController(this),
buttonController,
- isDesktopMode
- ? new DesktopNavbarButtonsViewController(this, mNavigationBarPanelContext,
- navButtonsView)
- : new NavbarButtonsViewController(this, mNavigationBarPanelContext,
- navButtonsView),
+ new NavbarButtonsViewController(this, mNavigationBarPanelContext, navButtonsView),
rotationButtonController,
new TaskbarDragLayerController(this, mDragLayer),
new TaskbarViewController(this, taskbarView),
@@ -285,7 +279,7 @@
new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
mWindowManager,
new RotationChangeProvider(c.getSystemService(DisplayManager.class), this,
- getMainThreadHandler())),
+ UI_HELPER_EXECUTOR.getHandler(), getMainThreadHandler())),
new TaskbarKeyguardController(this),
new StashedHandleViewController(this, stashedHandleView),
new TaskbarStashController(this),
@@ -298,12 +292,13 @@
new VoiceInteractionWindowController(this),
new TaskbarTranslationController(this),
new TaskbarSpringOnStashController(this),
- isDesktopMode
- ? new DesktopTaskbarRecentAppsController(this)
- : TaskbarRecentAppsController.DEFAULT,
- new TaskbarEduTooltipController(this),
+ new TaskbarRecentAppsController(
+ RecentsModel.INSTANCE.get(this),
+ LauncherActivityInterface.INSTANCE::getDesktopVisibilityController),
+ TaskbarEduTooltipController.newInstance(this),
new KeyboardQuickSwitchController(),
- new TaskbarPinningController(this),
+ new TaskbarPinningController(this, () ->
+ DisplayController.INSTANCE.get(this).getInfo().isInDesktopMode()),
bubbleControllersOptional);
mLauncherPrefs = LauncherPrefs.get(this);
@@ -402,7 +397,9 @@
* single window for taskbar and navbar.
*/
public boolean isPhoneMode() {
- return ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone;
+ return ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ && mDeviceProfile.isPhone
+ && !mDeviceProfile.isTaskbarPresent;
}
/**
@@ -419,6 +416,11 @@
return isPhoneMode() && !isThreeButtonNav();
}
+ /** Returns {@code true} iff a tiny version of taskbar is shown on phone. */
+ public boolean isTinyTaskbar() {
+ return enableTinyTaskbar() && mDeviceProfile.isPhone && mDeviceProfile.isTaskbarPresent;
+ }
+
/**
* Returns if software keyboard is docked or input toolbar is placed at the taskbar area
*/
@@ -603,12 +605,9 @@
return mImeDrawsImeNavBar;
}
- public int getLeftCornerRadius() {
- return mLeftCorner == null ? 0 : mLeftCorner.getRadius();
- }
-
- public int getRightCornerRadius() {
- return mRightCorner == null ? 0 : mRightCorner.getRadius();
+ public int getCornerRadius() {
+ return isPhoneMode() ? 0 : getResources().getDimensionPixelSize(
+ R.dimen.persistent_taskbar_corner_radius);
}
public WindowManager.LayoutParams getWindowLayoutParams() {
@@ -662,7 +661,7 @@
LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
LauncherAtom.TaskBarContainer.newBuilder();
- if (mControllers.uiController.isInOverview()) {
+ if (mControllers.uiController.isInOverviewUi()) {
taskbarBuilder.setTaskSwitcherContainer(
LauncherAtom.TaskSwitcherContainer.newBuilder());
}
@@ -813,7 +812,8 @@
return mIsDestroyed;
}
- public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) {
+ public void updateSysuiStateFlags(@SystemUiStateFlags long systemUiStateFlags,
+ boolean fromInit) {
mControllers.navbarButtonsViewController.updateStateForSysuiFlags(systemUiStateFlags,
fromInit);
boolean isShadeVisible = (systemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0;
@@ -944,12 +944,18 @@
}
if (landscapePhoneButtonNav) {
mWindowLayoutParams.width = size;
- mWindowLayoutParams.paramsForRotation[getDisplay().getRotation()].width = size;
+ for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ mWindowLayoutParams.paramsForRotation[rot].width = size;
+ }
} else {
mWindowLayoutParams.height = size;
- mWindowLayoutParams.paramsForRotation[getDisplay().getRotation()].height = size;
+ for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ mWindowLayoutParams.paramsForRotation[rot].height = size;
+ }
}
- mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
+ mControllers.runAfterInit(
+ mControllers.taskbarInsetsController
+ ::onTaskbarOrBubblebarWindowHeightOrInsetsChanged);
notifyUpdateLayoutParams();
}
@@ -960,7 +966,7 @@
public int getDefaultTaskbarWindowSize() {
Resources resources = getResources();
- if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone) {
+ if (isPhoneMode()) {
return isThreeButtonNav() ?
resources.getDimensionPixelSize(R.dimen.taskbar_phone_size) :
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
@@ -994,7 +1000,7 @@
return mDeviceProfile.taskbarHeight
- + Math.max(getLeftCornerRadius(), getRightCornerRadius())
+ + getCornerRadius()
+ extraHeightForTaskbarTooltips;
}
@@ -1080,20 +1086,19 @@
ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
ActivityOptions.makeBasic());
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
- } else if (tag instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_FOLDER) {
+ } else if (tag instanceof FolderInfo) {
// Tapping an expandable folder icon on Taskbar
shouldCloseAllOpenViews = false;
expandFolder((FolderIcon) view);
- } else if (tag instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_APP_PAIR) {
+ } else if (tag instanceof AppPairInfo api) {
// Tapping an app pair icon on Taskbar
if (recents != null && recents.isSplitSelectionActive()) {
- // TODO (b/274835596): Implement "can't split with this" bounce animation
Toast.makeText(this, "Unable to split with an app pair. Select another app.",
Toast.LENGTH_SHORT).show();
} else {
// Else launch the selected app pair
- launchFromTaskbar(recents, view, fi.contents);
- mControllers.uiController.onTaskbarIconLaunched(fi);
+ launchFromTaskbar(recents, view, api.getContents());
+ mControllers.uiController.onTaskbarIconLaunched(api);
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
}
} else if (tag instanceof WorkspaceItemInfo) {
@@ -1114,7 +1119,7 @@
} else if (info.isPromise()) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "start: taskbarPromiseIcon");
- intent = ApiWrapper.getAppMarketActivityIntent(this,
+ intent = ApiWrapper.INSTANCE.get(this).getAppMarketActivityIntent(
info.getTargetPackage(), Process.myUserHandle());
startActivity(intent);
@@ -1211,7 +1216,7 @@
.handleAppPairLaunchInApp((AppPairIcon) launchingIconView, itemInfos);
} else {
// Tapped a single app, nothing complicated here.
- startItemInfoActivity(itemInfos.get(0));
+ startItemInfoActivity(itemInfos.get(0), null /*foundTask*/);
}
}
@@ -1227,13 +1232,13 @@
return;
}
- boolean findExactPairMatch = itemInfos.size() == 2;
+ boolean isLaunchingAppPair = itemInfos.size() == 2;
// Convert the list of ItemInfo instances to a list of ComponentKeys
List<ComponentKey> componentKeys =
itemInfos.stream().map(ItemInfo::getComponentKey).toList();
recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
componentKeys,
- findExactPairMatch,
+ isLaunchingAppPair,
foundTasks -> {
@Nullable Task foundTask = foundTasks[0];
if (foundTask != null) {
@@ -1247,24 +1252,50 @@
}
}
- if (findExactPairMatch) {
- // We did not find the app pair we were looking for, so launch one.
- recents.getSplitSelectController().getAppPairsController().launchAppPair(
- (AppPairIcon) launchingIconView);
+ if (isLaunchingAppPair) {
+ // Finish recents animation if it's running before launching to ensure
+ // we get both leashes for the animation
+ mControllers.uiController.setSkipNextRecentsAnimEnd();
+ recents.switchToScreenshot(() ->
+ recents.finishRecentsAnimation(true /*toRecents*/,
+ false /*shouldPip*/,
+ () -> recents
+ .getSplitSelectController()
+ .getAppPairsController()
+ .launchAppPair((AppPairIcon) launchingIconView,
+ -1 /*cuj*/)));
} else {
- startItemInfoActivity(itemInfos.get(0));
+ startItemInfoActivity(itemInfos.get(0), foundTask);
}
}
);
}
- private void startItemInfoActivity(ItemInfo info) {
+ /**
+ * Starts an activity with the information provided by the "info" param. However, if
+ * taskInRecents is present, it will prioritize re-launching an existing instance via
+ * {@link ActivityManagerWrapper#startActivityFromRecents(int, ActivityOptions)}
+ */
+ private void startItemInfoActivity(ItemInfo info, @Nullable Task taskInRecents) {
Intent intent = new Intent(info.getIntent())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon");
if (info.user.equals(Process.myUserHandle())) {
// TODO(b/216683257): Use startActivityForResult for search results that require it.
+ if (taskInRecents != null) {
+ // Re launch instance from recents
+ ActivityOptionsWrapper opts = getActivityLaunchOptions(null, info);
+ opts.options.setLaunchDisplayId(
+ getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
+ if (ActivityManagerWrapper.getInstance()
+ .startActivityFromRecents(taskInRecents.key, opts.options)) {
+ mControllers.uiController.getRecentsView()
+ .addSideTaskLaunchCallback(opts.onEndCallback);
+ return;
+ }
+ }
+
startActivity(intent);
} else {
getSystemService(LauncherApps.class).startMainActivity(
@@ -1323,8 +1354,12 @@
* Called when we want to unstash taskbar when user performs swipes up gesture.
*/
public void onSwipeToUnstashTaskbar() {
- VibratorWrapper.INSTANCE.get(this).vibrateForTaskbarUnstash();
+ boolean wasStashed = mControllers.taskbarStashController.isStashed();
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false);
+ boolean isStashed = mControllers.taskbarStashController.isStashed();
+ if (isStashed != wasStashed) {
+ VibratorWrapper.INSTANCE.get(this).vibrateForTaskbarUnstash();
+ }
mControllers.taskbarEduTooltipController.hide();
}
@@ -1348,6 +1383,13 @@
}
/**
+ * Plays the taskbar background alpha animation if one is not currently playing.
+ */
+ public void playTaskbarBackgroundAlphaAnimation() {
+ mControllers.taskbarStashController.playTaskbarBackgroundAlphaAnimation();
+ }
+
+ /**
* Called to start the taskbar translation spring to its settled translation (0).
*/
public void startTranslationSpring() {
@@ -1406,24 +1448,7 @@
});
}
- public void hideTaskbarWhenFolding() {
- AnimatedFloat alphaAnim = mControllers.taskbarDragLayerController.getTaskbarAlpha();
- alphaAnim.cancelAnimation();
- alphaAnim.updateValue(0);
- ObjectAnimator animator = alphaAnim.animateToValue(1).setDuration(0);
- animator.setStartDelay(TASKBAR_REAPPEAR_DELAY_MS);
- animator.start();
- }
-
- public void cancelHideTaskbarWhenFolding() {
- mControllers.taskbarDragLayerController.getTaskbarAlpha().cancelAnimation();
- }
-
- public void resetHideTaskbarWhenUnfolding() {
- mControllers.taskbarDragLayerController.getTaskbarAlpha().updateValue(1);
- }
-
- protected boolean isUserSetupComplete() {
+ public boolean isUserSetupComplete() {
return mIsUserSetupComplete;
}
@@ -1459,7 +1484,8 @@
((LauncherTaskbarUIController) uiController).addLauncherVisibilityChangedAnimation(
fullAnimation, duration);
}
- mControllers.taskbarStashController.addUnstashToHotseatAnimation(fullAnimation, duration);
+ mControllers.taskbarStashController.addUnstashToHotseatAnimationFromSuw(fullAnimation,
+ duration);
View allAppsButton = mControllers.taskbarViewController.getAllAppsButtonView();
if (allAppsButton != null && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) {
@@ -1563,4 +1589,9 @@
boolean canToggleHomeAllApps() {
return mControllers.uiController.canToggleHomeAllApps();
}
+
+ @VisibleForTesting
+ public TaskbarControllers getControllers() {
+ return mControllers;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index e290c3f..2737cbd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -37,8 +37,6 @@
class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
private val isInSetup: Boolean = !context.isUserSetupComplete
- private val DARK_THEME_SHADOW_ALPHA = 51f
- private val LIGHT_THEME_SHADOW_ALPHA = 25f
private val maxTransientTaskbarHeight =
context.transientTaskbarDeviceProfile.taskbarHeight.toFloat()
@@ -54,6 +52,7 @@
var isAnimatingPinning = false
val paint = Paint()
+ private val strokePaint = Paint()
val lastDrawnTransientRect = RectF()
var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
var translationYForSwipe = 0f
@@ -62,19 +61,18 @@
private val transientBackgroundBounds = context.transientTaskbarBounds
private val shadowAlpha: Float
+ private val strokeAlpha: Int
private var shadowBlur = 0f
private var keyShadowDistance = 0f
private var bottomMargin = 0
- private val fullLeftCornerRadius = context.leftCornerRadius.toFloat()
- private val fullRightCornerRadius = context.rightCornerRadius.toFloat()
- private var leftCornerRadius = fullLeftCornerRadius
- private var rightCornerRadius = fullRightCornerRadius
+ private val fullCornerRadius = context.cornerRadius.toFloat()
+ private var cornerRadius = fullCornerRadius
private var widthInsetPercentage = 0f
- private val square: Path = Path()
- private val circle: Path = Path()
- private val invertedLeftCornerPath: Path = Path()
- private val invertedRightCornerPath: Path = Path()
+ private val square = Path()
+ private val circle = Path()
+ private val invertedLeftCornerPath = Path()
+ private val invertedRightCornerPath = Path()
private var stashedHandleWidth =
context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width)
@@ -86,10 +84,18 @@
paint.color = context.getColor(R.color.taskbar_background)
paint.flags = Paint.ANTI_ALIAS_FLAG
paint.style = Paint.Style.FILL
-
- shadowAlpha =
- if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
- else LIGHT_THEME_SHADOW_ALPHA
+ strokePaint.color = context.getColor(R.color.taskbar_stroke)
+ strokePaint.flags = Paint.ANTI_ALIAS_FLAG
+ strokePaint.style = Paint.Style.STROKE
+ strokePaint.strokeWidth =
+ context.resources.getDimension(R.dimen.transient_taskbar_stroke_width)
+ if (Utilities.isDarkTheme(context)) {
+ strokeAlpha = DARK_THEME_STROKE_ALPHA
+ shadowAlpha = DARK_THEME_SHADOW_ALPHA
+ } else {
+ strokeAlpha = LIGHT_THEME_STROKE_ALPHA
+ shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
+ }
setCornerRoundness(DEFAULT_ROUNDNESS)
}
@@ -97,13 +103,16 @@
fun updateStashedHandleWidth(context: TaskbarActivityContext, res: Resources) {
stashedHandleWidth =
res.getDimensionPixelSize(
- if (context.isPhoneMode) R.dimen.taskbar_stashed_small_screen
- else R.dimen.taskbar_stashed_handle_width
+ if (context.isPhoneMode || context.isTinyTaskbar) {
+ R.dimen.taskbar_stashed_small_screen
+ } else {
+ R.dimen.taskbar_stashed_handle_width
+ }
)
}
/**
- * Sets the roundness of the round corner above Taskbar. No effect on transient Taskkbar.
+ * Sets the roundness of the round corner above Taskbar. No effect on transient Taskbar.
*
* @param cornerRoundness 0 has no round corner, 1 has complete round corner.
*/
@@ -112,21 +121,18 @@
return
}
- leftCornerRadius = fullLeftCornerRadius * cornerRoundness
- rightCornerRadius = fullRightCornerRadius * cornerRoundness
+ cornerRadius = fullCornerRadius * cornerRoundness
// Create the paths for the inverted rounded corners above the taskbar. Start with a filled
// square, and then subtract out a circle from the appropriate corner.
square.reset()
- square.addRect(0f, 0f, leftCornerRadius, leftCornerRadius, Path.Direction.CW)
+ square.addRect(0f, 0f, cornerRadius, cornerRadius, Path.Direction.CW)
circle.reset()
- circle.addCircle(leftCornerRadius, 0f, leftCornerRadius, Path.Direction.CW)
+ circle.addCircle(cornerRadius, 0f, cornerRadius, Path.Direction.CW)
invertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE)
- square.reset()
- square.addRect(0f, 0f, rightCornerRadius, rightCornerRadius, Path.Direction.CW)
circle.reset()
- circle.addCircle(0f, 0f, rightCornerRadius, Path.Direction.CW)
+ circle.addCircle(0f, 0f, cornerRadius, Path.Direction.CW)
invertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE)
}
@@ -160,10 +166,10 @@
}
// Draw the inverted rounded corners above the taskbar.
- canvas.translate(0f, -leftCornerRadius)
+ canvas.translate(0f, -cornerRadius)
canvas.drawPath(invertedLeftCornerPath, paint)
- canvas.translate(0f, leftCornerRadius)
- canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
+ canvas.translate(0f, cornerRadius)
+ canvas.translate(canvas.width - cornerRadius, -cornerRadius)
canvas.drawPath(invertedRightCornerPath, paint)
}
@@ -233,6 +239,7 @@
keyShadowDistance,
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
)
+ strokePaint.alpha = (paint.alpha * strokeAlpha) / 255
lastDrawnTransientRect.set(
transientBackgroundBounds.left + halfWidthDelta,
@@ -244,6 +251,7 @@
lastDrawnTransientRect.inset(horizontalInset, 0f)
canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
+ canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, strokePaint)
}
/**
@@ -256,5 +264,9 @@
companion object {
const val DEFAULT_ROUNDNESS = 1f
+ private const val DARK_THEME_STROKE_ALPHA = 51
+ private const val LIGHT_THEME_STROKE_ALPHA = 41
+ private const val DARK_THEME_SHADOW_ALPHA = 51f
+ private const val LIGHT_THEME_SHADOW_ALPHA = 25f
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index f9ddc3d..58c5e83 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -180,8 +180,9 @@
taskbarUnfoldAnimationController, taskbarKeyguardController,
stashedHandleViewController, taskbarStashController,
taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController,
- voiceInteractionWindowController, taskbarTranslationController,
- taskbarEduTooltipController, keyboardQuickSwitchController, taskbarPinningController
+ voiceInteractionWindowController, taskbarRecentAppsController,
+ taskbarTranslationController, taskbarEduTooltipController,
+ keyboardQuickSwitchController, taskbarPinningController,
};
mBackgroundRendererControllers = new BackgroundRendererController[] {
taskbarDragLayerController, taskbarScrimViewController,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index 12f1e63..a635537 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -69,8 +69,6 @@
private lateinit var dividerView: View
- private val menuWidth =
- resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width)
private val popupCornerRadius = Themes.getDialogCornerRadius(context)
private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
@@ -98,16 +96,21 @@
popupContainer.getDescendantRectRelativeToSelf(dividerView, outPos)
}
- @SuppressLint("UseSwitchCompatOrMaterialCode")
+ @SuppressLint("UseSwitchCompatOrMaterialCode", "ClickableViewAccessibility")
override fun onFinishInflate() {
super.onFinishInflate()
val taskbarSwitchOption = requireViewById<LinearLayout>(R.id.taskbar_switch_option)
val alwaysShowTaskbarSwitch = requireViewById<Switch>(R.id.taskbar_pinning_switch)
val taskbarVisibilityIcon = requireViewById<View>(R.id.taskbar_pinning_visibility_icon)
+
alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn
+ alwaysShowTaskbarSwitch.setOnTouchListener { view, event ->
+ (view.parent as View).onTouchEvent(event)
+ }
+ alwaysShowTaskbarSwitch.setOnClickListener { view -> (view.parent as View).performClick() }
+
if (ActivityContext.lookupContext<TaskbarActivityContext>(context).isGestureNav) {
taskbarSwitchOption.setOnClickListener {
- alwaysShowTaskbarSwitch.isClickable = true
alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
onClickAlwaysShowTaskbarSwitchOption()
}
@@ -125,7 +128,7 @@
/** Orient object as usual and then center object horizontally. */
override fun orientAboutObject() {
super.orientAboutObject()
- x = mTempRect.centerX() - menuWidth / 2f
+ x = mTempRect.centerX() - measuredWidth / 2f
}
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index faa67be..4f5922c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -74,12 +74,14 @@
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.views.BubbleTextHolder;
+import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.util.LogUtils;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.systemui.shared.recents.model.Task;
@@ -163,11 +165,6 @@
return false;
}
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onTaskbarItemLongClick");
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.TWO_TASKBAR_LONG_CLICKS,
- "TaskbarDragController.startDragOnLongClick",
- new Throwable());
- }
BubbleTextView btv = (BubbleTextView) view;
mActivity.onDragStart();
btv.post(() -> {
@@ -340,8 +337,13 @@
@Override
protected void callOnDragStart() {
super.callOnDragStart();
+ // TODO(297921594) clean it up when taskbar to desktop drag is implemented.
+ DesktopVisibilityController desktopController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+
// Pre-drag has ended, start the global system drag.
- if (mDisallowGlobalDrag) {
+ if (mDisallowGlobalDrag || (desktopController != null
+ && desktopController.areDesktopTasksVisible())) {
AbstractFloatingView.closeAllOpenViewsExcept(mActivity, TYPE_TASKBAR_ALL_APPS);
return;
}
@@ -686,15 +688,10 @@
float toScale = iconSize / mDragIconSize;
float toAlpha = (target == originalView) ? 1f : 0f;
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
- final FloatProp mDx = new FloatProp(fromX, toPosition[0], 0,
- ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.FAST_OUT_SLOW_IN);
- final FloatProp mDy = new FloatProp(fromY, toPosition[1], 0,
- ANIM_DURATION_RETURN_ICON_TO_TASKBAR,
- FAST_OUT_SLOW_IN);
- final FloatProp mScale = new FloatProp(1f, toScale, 0,
- ANIM_DURATION_RETURN_ICON_TO_TASKBAR, FAST_OUT_SLOW_IN);
- final FloatProp mAlpha = new FloatProp(1f, toAlpha, 0,
- ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.ACCELERATE_2);
+ final FloatProp mDx = new FloatProp(fromX, toPosition[0], FAST_OUT_SLOW_IN);
+ final FloatProp mDy = new FloatProp(fromY, toPosition[1], FAST_OUT_SLOW_IN);
+ final FloatProp mScale = new FloatProp(1f, toScale, FAST_OUT_SLOW_IN);
+ final FloatProp mAlpha = new FloatProp(1f, toAlpha, Interpolators.ACCELERATE_2);
@Override
public void onUpdate(float percent, boolean initOnly) {
animListener.updateDragShadow(mDx.value, mDy.value, mScale.value, mAlpha.value);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 7f201b4..f703463 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -18,6 +18,7 @@
import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_BACK;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import android.content.Context;
@@ -37,10 +38,11 @@
import androidx.core.graphics.Insets;
import androidx.core.view.WindowInsetsCompat;
-import com.android.app.viewcapture.SettingsAwareViewCapture;
+import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.views.BaseDragLayer;
@@ -104,7 +106,10 @@
mTaskbarBackgroundAlpha = new MultiPropertyFactory<>(this, BG_ALPHA, INDEX_COUNT,
(a, b) -> a * b, 1f);
mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES).setValue(0);
- mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM).setValue(1);
+ mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM).setValue(
+ enableScalingRevealHomeAnimation() && DisplayController.isTransientTaskbar(context)
+ ? 0
+ : 1);
}
public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
@@ -150,7 +155,7 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnComputeInternalInsetsListener(mTaskbarInsetsComputer);
- mViewCaptureCloseable = SettingsAwareViewCapture.getInstance(getContext())
+ mViewCaptureCloseable = ViewCaptureFactory.getInstance(getContext())
.startCapture(getRootView(), ".Taskbar");
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index e48c20d..ff890fb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -44,12 +44,6 @@
private static final boolean DEBUG = SystemProperties.getBoolean(
"persist.debug.draw_taskbar_debug_ui", false);
- // Delay to reset the task bar alpha back to 1 after fading it for transition from unfold to
- // fold. Normally this is not needed since the new task bar is recreated after fading, but in
- // case something goes wrong this provides a fallback mechanism to make sure the task bar is
- // visible after the transition finishes.
- public static final long TASKBAR_REAPPEAR_DELAY_MS = 2000;
-
private final TaskbarActivityContext mActivity;
private final TaskbarDragLayer mTaskbarDragLayer;
private final int mFolderMargin;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
index c45c667..7f9d8a3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
@@ -27,6 +27,7 @@
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.animation.Interpolator
+import android.window.OnBackInvokedDispatcher
import androidx.core.view.updateLayoutParams
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
@@ -66,11 +67,14 @@
/** Container where the tooltip's body should be inflated. */
lateinit var content: ViewGroup
private set
+
private lateinit var arrow: View
/** Callback invoked when the tooltip is being closed. */
var onCloseCallback: () -> Unit = {}
private var openCloseAnimator: AnimatorSet? = null
+ /** Used to set whether users can tap outside the current tooltip window to dismiss it */
+ var allowTouchDismissal = true
/** Animates the tooltip into view. */
fun show() {
@@ -134,14 +138,25 @@
override fun isOfType(type: Int): Boolean = type and TYPE_TASKBAR_EDUCATION_DIALOG != 0
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
- if (ev?.action == ACTION_DOWN && !activityContext.dragLayer.isEventOverView(this, ev)) {
+ if (
+ ev?.action == ACTION_DOWN &&
+ !activityContext.dragLayer.isEventOverView(this, ev) &&
+ allowTouchDismissal
+ ) {
close(true)
}
return false
}
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ findOnBackInvokedDispatcher()
+ ?.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, this)
+ }
+
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
+ findOnBackInvokedDispatcher()?.unregisterOnBackInvokedCallback(this)
Settings.Secure.putInt(mContext.contentResolver, LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0)
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index 7eed955..d57c483 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -15,7 +15,13 @@
*/
package com.android.launcher3.taskbar
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
import android.os.Bundle
+import android.text.SpannableString
+import android.text.method.LinkMovementMethod
+import android.text.style.URLSpan
import android.view.Gravity
import android.view.View
import android.view.View.GONE
@@ -24,10 +30,13 @@
import android.view.ViewGroup.MarginLayoutParams
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.TextView
import androidx.annotation.IntDef
import androidx.annotation.LayoutRes
+import androidx.core.text.HtmlCompat
import androidx.core.view.updateLayoutParams
import com.airbnb.lottie.LottieAnimationView
+import com.android.launcher3.LauncherPrefs
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
@@ -35,6 +44,9 @@
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
+import com.android.launcher3.util.OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN
+import com.android.launcher3.util.ResourceBasedOverride
+import com.android.launcher3.views.ActivityContext
import com.android.launcher3.views.BaseDragLayer
import com.android.quickstep.util.LottieAnimationColorUtils
import java.io.PrintWriter
@@ -52,6 +64,10 @@
* This value should match the maximum count for [TASKBAR_EDU_TOOLTIP_STEP].
*/
const val TOOLTIP_STEP_NONE = 3
+/** The base URL for the Privacy Policy that will later be localized. */
+private const val PRIVACY_POLICY_BASE_URL = "https://policies.google.com/privacy/embedded?hl="
+/** The base URL for the Terms of Service that will later be localized. */
+private const val TOS_BASE_URL = "https://policies.google.com/terms?hl="
/** Current step in the tooltip EDU flow. */
@Retention(AnnotationRetention.SOURCE)
@@ -59,17 +75,35 @@
annotation class TaskbarEduTooltipStep
/** Controls stepping through the Taskbar tooltip EDU. */
-class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
- LoggableTaskbarController {
+open class TaskbarEduTooltipController(context: Context) :
+ ResourceBasedOverride, LoggableTaskbarController {
+ protected val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
+ open val shouldShowSearchEdu = false
private val isTooltipEnabled: Boolean
- get() = !Utilities.isRunningInTestHarness() && !activityContext.isPhoneMode
+ get() {
+ return !Utilities.isRunningInTestHarness() &&
+ !activityContext.isPhoneMode &&
+ !activityContext.isTinyTaskbar
+ }
+
private val isOpen: Boolean
get() = tooltip?.isOpen ?: false
+
val isBeforeTooltipFeaturesStep: Boolean
get() = isTooltipEnabled && tooltipStep <= TOOLTIP_STEP_FEATURES
+
private lateinit var controllers: TaskbarControllers
+ // Keep track of whether the user has seen the Search Edu
+ private var userHasSeenSearchEdu: Boolean
+ get() {
+ return TASKBAR_SEARCH_EDU_SEEN.get(activityContext)
+ }
+ private set(seen) {
+ LauncherPrefs.get(activityContext).put(TASKBAR_SEARCH_EDU_SEEN, seen)
+ }
+
@TaskbarEduTooltipStep
var tooltipStep: Int
get() {
@@ -83,6 +117,8 @@
fun init(controllers: TaskbarControllers) {
this.controllers = controllers
+ // We want to show the Search Edu right after pinning the taskbar, so we post it here
+ activityContext.dragLayer.post { maybeShowSearchEdu() }
}
/** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
@@ -112,12 +148,14 @@
fun maybeShowFeaturesEdu() {
if (!isTooltipEnabled || tooltipStep > TOOLTIP_STEP_FEATURES) {
maybeShowPinningEdu()
+ maybeShowSearchEdu()
return
}
tooltipStep = TOOLTIP_STEP_NONE
inflateTooltip(R.layout.taskbar_edu_features)
tooltip?.run {
+ allowTouchDismissal = false
val splitscreenAnim = requireViewById<LottieAnimationView>(R.id.splitscreen_animation)
val suggestionsAnim = requireViewById<LottieAnimationView>(R.id.suggestions_animation)
val pinningAnim = requireViewById<LottieAnimationView>(R.id.pinning_animation)
@@ -182,6 +220,7 @@
inflateTooltip(R.layout.taskbar_edu_pinning)
tooltip?.run {
+ allowTouchDismissal = true
requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
.supportLightTheme()
@@ -207,8 +246,101 @@
}
}
+ /**
+ * Shows standalone Search EDU tooltip if this EDU has not been seen.
+ *
+ * We show this standalone edu for users to learn to how to trigger Search from the pinned
+ * taskbar
+ */
+ fun maybeShowSearchEdu() {
+ if (
+ !enableTaskbarPinning() ||
+ !DisplayController.isPinnedTaskbar(activityContext) ||
+ !isTooltipEnabled ||
+ !shouldShowSearchEdu ||
+ userHasSeenSearchEdu
+ ) {
+ return
+ }
+ userHasSeenSearchEdu = true
+ inflateTooltip(R.layout.taskbar_edu_search)
+ tooltip?.run {
+ allowTouchDismissal = true
+ requireViewById<LottieAnimationView>(R.id.search_edu_animation).supportLightTheme()
+ val eduSubtitle: TextView = requireViewById(R.id.search_edu_text)
+ showDisclosureText(eduSubtitle)
+ updateLayoutParams<BaseDragLayer.LayoutParams> {
+ if (DisplayController.isTransientTaskbar(activityContext)) {
+ bottomMargin += activityContext.deviceProfile.taskbarHeight
+ }
+ // Unlike other tooltips, we want to align with the all apps button rather than
+ // center.
+ gravity = Gravity.BOTTOM
+ marginStart = 0
+ width =
+ resources.getDimensionPixelSize(
+ R.dimen.taskbar_edu_features_tooltip_width_with_one_feature
+ )
+ }
+
+ // Calculate the amount the tooltip must be shifted by to align with the action key
+ val allAppsButtonView = controllers.taskbarViewController.allAppsButtonView
+ if (allAppsButtonView != null) {
+ val allAppsIconLocation = allAppsButtonView.x + allAppsButtonView.width / 2
+ x = allAppsIconLocation - layoutParams.width / 2
+ }
+
+ show()
+ }
+ }
+
+ /**
+ * Set up the provided TextView to display legal disclosures. The method takes locale into
+ * account to show the appropriate links to regional disclosures.
+ */
+ private fun TaskbarEduTooltip.showDisclosureText(
+ textView: TextView,
+ stringId: Int = R.string.taskbar_edu_search_disclosure,
+ ) {
+ val locale = resources.configuration.locales[0]
+ val text =
+ SpannableString(
+ HtmlCompat.fromHtml(
+ resources.getString(
+ stringId,
+ PRIVACY_POLICY_BASE_URL + locale.language,
+ TOS_BASE_URL + locale.language,
+ ),
+ HtmlCompat.FROM_HTML_MODE_COMPACT,
+ )
+ )
+ // Directly process URLSpan clicks
+ text.getSpans(0, text.length, URLSpan::class.java).forEach { urlSpan ->
+ val url: URLSpan =
+ object : URLSpan(urlSpan.url) {
+ override fun onClick(widget: View) {
+ val uri = Uri.parse(urlSpan.url)
+ val context = widget.context
+ val intent =
+ Intent(Intent.ACTION_VIEW, uri).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ }
+ }
+
+ val spanStart = text.getSpanStart(urlSpan)
+ val spanEnd = text.getSpanEnd(urlSpan)
+ val spanFlags = text.getSpanFlags(urlSpan)
+ text.removeSpan(urlSpan)
+ text.setSpan(url, spanStart, spanEnd, spanFlags)
+ }
+ textView.text = text
+ textView.movementMethod = LinkMovementMethod.getInstance()
+ }
+
/** Closes the current [tooltip]. */
- fun hide() = tooltip?.close(true)
+ fun hide() {
+ tooltip?.close(true)
+ }
/** Initializes [tooltip] with content from [contentResId]. */
private fun inflateTooltip(@LayoutRes contentResId: Int) {
@@ -280,6 +412,17 @@
pw?.println("$prefix\tisOpen=$isOpen")
pw?.println("$prefix\ttooltipStep=$tooltipStep")
}
+
+ companion object {
+ @JvmStatic
+ fun newInstance(context: Context): TaskbarEduTooltipController {
+ return ResourceBasedOverride.Overrides.getObject(
+ TaskbarEduTooltipController::class.java,
+ context,
+ R.string.taskbar_edu_tooltip_controller_class
+ )
+ }
+ }
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java
index 333c07b..6ac862e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java
@@ -34,6 +34,7 @@
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.TouchController;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
/**
* Controller for taskbar when force visible in immersive mode is set.
@@ -83,7 +84,7 @@
}
/** Update values tracked via sysui flags. */
- public void updateSysuiFlags(int sysuiFlags) {
+ public void updateSysuiFlags(@SystemUiStateFlags long sysuiFlags) {
mIsImmersiveMode = (sysuiFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) == 0;
if (mContext.isNavBarForceVisible()) {
if (mIsImmersiveMode) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 567fad0..2103ebb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -28,6 +28,7 @@
import android.view.Gravity
import android.view.InsetsFrameProvider
import android.view.InsetsFrameProvider.SOURCE_DISPLAY
+import android.view.InsetsSource.FLAG_ANIMATE_RESIZING
import android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER
import android.view.InsetsSource.FLAG_SUPPRESS_SCRIM
import android.view.Surface
@@ -50,7 +51,9 @@
import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION
import com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
+import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.Executors
import java.io.PrintWriter
import kotlin.jvm.optionals.getOrNull
import kotlin.math.max
@@ -74,6 +77,7 @@
private val gestureNavSettingsObserver =
GestureNavigationSettingsObserver(
context.mainThreadHandler,
+ Executors.UI_HELPER_EXECUTOR.handler,
context,
this::onTaskbarOrBubblebarWindowHeightOrInsetsChanged
)
@@ -152,6 +156,19 @@
context.deviceProfile.widthPx,
windowLayoutParams.height
)
+
+ // if there's an animating bubble add it to the touch region so that it's clickable
+ val isAnimatingNewBubble =
+ controllers.bubbleControllers
+ .getOrNull()
+ ?.bubbleBarViewController
+ ?.isAnimatingNewBubble
+ ?: false
+ if (isAnimatingNewBubble) {
+ val iconBounds =
+ controllers.bubbleControllers.get().bubbleBarViewController.bubbleBarBounds
+ defaultTouchableRegion.op(iconBounds, Region.Op.UNION)
+ }
}
// Pre-calculate insets for different providers across different rotations for this gravity
@@ -162,6 +179,10 @@
setProviderInsets(provider, layoutParams.gravity, rotation)
}
}
+ // Also set the parent providers (i.e. not in paramsForRotation).
+ for (provider in windowLayoutParams.providedInsets) {
+ setProviderInsets(provider, windowLayoutParams.gravity, context.display.rotation)
+ }
context.notifyUpdateLayoutParams()
}
@@ -192,10 +213,14 @@
*/
private fun getProvidedInsets(insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
val navBarsFlag =
- (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
+ (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM or FLAG_ANIMATE_RESIZING else 0) or
+ insetsRoundedCornerFlag
return arrayOf(
InsetsFrameProvider(insetsOwner, 0, navigationBars())
- .setFlags(navBarsFlag, FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER),
+ .setFlags(
+ navBarsFlag,
+ FLAG_SUPPRESS_SCRIM or FLAG_ANIMATE_RESIZING or FLAG_INSETS_ROUNDED_CORNER
+ ),
InsetsFrameProvider(insetsOwner, 0, tappableElement()),
InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
@@ -209,8 +234,25 @@
val contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
val res = context.resources
- if (provider.type == navigationBars() || provider.type == mandatorySystemGestures()) {
+ if (provider.type == navigationBars()) {
provider.insetsSize = getInsetsForGravityWithCutout(contentHeight, gravity, endRotation)
+ } else if (provider.type == mandatorySystemGestures()) {
+ if (context.isThreeButtonNav) {
+ provider.insetsSize = getInsetsForGravityWithCutout(contentHeight, gravity,
+ endRotation)
+ } else {
+ val gestureHeight =
+ ResourceUtils.getNavbarSize(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
+ context.resources)
+ val isPinnedTaskbar = context.deviceProfile.isTaskbarPresent
+ && !context.deviceProfile.isTransientTaskbar
+ val mandatoryGestureHeight =
+ if (isPinnedTaskbar) contentHeight
+ else gestureHeight
+ provider.insetsSize = getInsetsForGravityWithCutout(mandatoryGestureHeight, gravity,
+ endRotation)
+ }
} else if (provider.type == tappableElement()) {
provider.insetsSize = getInsetsForGravity(tappableHeight, gravity)
} else if (provider.type == systemGestures() && provider.index == INDEX_LEFT) {
@@ -366,7 +408,7 @@
) {
// Taskbar has some touchable elements, take over the full taskbar area
if (
- controllers.uiController.isInOverview &&
+ controllers.uiController.isInOverviewUi &&
DisplayController.isTransientTaskbar(context)
) {
val region =
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
index 03d08eb..911140a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
@@ -5,9 +5,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK;
@@ -16,7 +13,9 @@
import android.app.KeyguardManager;
import com.android.launcher3.AbstractFloatingView;
+import com.android.quickstep.util.SystemUiFlagUtils;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.PrintWriter;
@@ -25,21 +24,8 @@
*/
public class TaskbarKeyguardController implements TaskbarControllers.LoggableTaskbarController {
- private static final int KEYGUARD_SYSUI_FLAGS = SYSUI_STATE_BOUNCER_SHOWING
- | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_DEVICE_DOZING
- | SYSUI_STATE_OVERVIEW_DISABLED | SYSUI_STATE_HOME_DISABLED
- | SYSUI_STATE_BACK_DISABLED | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
- | SYSUI_STATE_WAKEFULNESS_MASK;
-
- // If any of these SysUi flags (via QuickstepContract) is set, the device to be considered
- // locked.
- public static final int MASK_ANY_SYSUI_LOCKED = SYSUI_STATE_BOUNCER_SHOWING
- | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
- | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
- | SYSUI_STATE_DEVICE_DREAMING;
-
private final TaskbarActivityContext mContext;
- private int mKeyguardSysuiFlags;
+ private long mKeyguardSysuiFlags;
private boolean mBouncerShowing;
private NavbarButtonsViewController mNavbarButtonsViewController;
private final KeyguardManager mKeyguardManager;
@@ -53,8 +39,8 @@
mNavbarButtonsViewController = navbarButtonUIController;
}
- public void updateStateForSysuiFlags(int systemUiStateFlags) {
- int interestingKeyguardFlags = systemUiStateFlags & KEYGUARD_SYSUI_FLAGS;
+ public void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags) {
+ long interestingKeyguardFlags = systemUiStateFlags & SystemUiFlagUtils.KEYGUARD_SYSUI_FLAGS;
if (interestingKeyguardFlags == mKeyguardSysuiFlags) {
// No change in keyguard relevant flags
return;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 8d83716..cb9f24a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar;
import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
@@ -51,9 +50,11 @@
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.util.SystemUiFlagUtils;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.animation.ViewRootSync;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.PrintWriter;
import java.util.HashMap;
@@ -65,7 +66,7 @@
*/
public class TaskbarLauncherStateController {
- private static final String TAG = TaskbarLauncherStateController.class.getSimpleName();
+ private static final String TAG = "TaskbarLauncherStateController";
private static final boolean DEBUG = false;
/** Launcher activity is visible and focused. */
@@ -148,6 +149,7 @@
private Integer mPrevState;
private int mState;
private LauncherState mLauncherState = LauncherState.NORMAL;
+ private boolean mSkipNextRecentsAnimEnd;
// Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds).
private long mLastUnlockTimeMs = 0;
@@ -193,7 +195,7 @@
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
if (!mShouldDelayLauncherStateAnim) {
if (toState == LauncherState.NORMAL) {
- applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
+ applyState(QuickstepTransitionManager.getTaskbarToHomeDuration());
} else {
applyState();
}
@@ -205,13 +207,7 @@
mLauncherState = finalState;
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
applyState();
- boolean disallowLongClick =
- FeatureFlags.enableSplitContextually()
- ? mLauncher.isSplitSelectionActive()
- : finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
- com.android.launcher3.taskbar.Utilities.setOverviewDragState(
- mControllers, finalState.disallowTaskbarGlobalDrag(),
- disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
+ updateOverviewDragState(finalState);
}
};
@@ -231,7 +227,7 @@
/** Initializes the controller instance, and applies the initial state immediately. */
public void init(TaskbarControllers controllers, QuickstepLauncher launcher,
- int sysuiStateFlags) {
+ @SystemUiStateFlags long sysuiStateFlags) {
mCanSyncViews = false;
mControllers = controllers;
@@ -256,6 +252,7 @@
mCanSyncViews = true;
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
+ updateOverviewDragState(mLauncherState);
}
public void onDestroy() {
@@ -297,12 +294,12 @@
if (mTaskBarRecentsAnimationListener != null) {
mTaskBarRecentsAnimationListener.endGestureStateOverride(
- !mLauncher.isInState(LauncherState.OVERVIEW));
+ !mLauncher.isInState(LauncherState.OVERVIEW), false /*canceled*/);
}
mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks);
callbacks.addListener(mTaskBarRecentsAnimationListener);
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() ->
- mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
+ mTaskBarRecentsAnimationListener.endGestureStateOverride(true, false /*canceled*/));
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchCancelledRunnable(() -> {
updateStateForUserFinishedToApp(false /* finishedToApp */);
@@ -323,12 +320,18 @@
mShouldDelayLauncherStateAnim = shouldDelayLauncherStateAnim;
}
+ /** Will make the next onRecentsAnimationFinished() a no-op. */
+ public void setSkipNextRecentsAnimEnd() {
+ mSkipNextRecentsAnimEnd = true;
+ }
+
/** SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values. */
- public void updateStateForSysuiFlags(int systemUiStateFlags) {
+ public void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags) {
updateStateForSysuiFlags(systemUiStateFlags, /* applyState */ true);
}
- private void updateStateForSysuiFlags(int systemUiStateFlags, boolean applyState) {
+ private void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags,
+ boolean applyState) {
final boolean prevIsAwake = hasAnyFlag(FLAG_AWAKE);
final boolean currIsAwake = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_AWAKE);
@@ -340,8 +343,7 @@
prevIsAwake && hasAnyFlag(FLAGS_LAUNCHER_ACTIVE));
}
- boolean isDeviceLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED);
- updateStateForFlag(FLAG_DEVICE_LOCKED, isDeviceLocked);
+ updateStateForFlag(FLAG_DEVICE_LOCKED, SystemUiFlagUtils.isLocked(systemUiStateFlags));
// Taskbar is hidden whenever the device is dreaming. The dreaming state includes the
// interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in
@@ -358,6 +360,21 @@
}
/**
+ * Updates overview drag state on various controllers based on {@link #mLauncherState}.
+ *
+ * @param launcherState The current state launcher is in
+ */
+ private void updateOverviewDragState(LauncherState launcherState) {
+ boolean disallowLongClick =
+ FeatureFlags.enableSplitContextually()
+ ? mLauncher.isSplitSelectionActive()
+ : launcherState == LauncherState.OVERVIEW_SPLIT_SELECT;
+ com.android.launcher3.taskbar.Utilities.setOverviewDragState(
+ mControllers, launcherState.disallowTaskbarGlobalDrag(),
+ disallowLongClick, launcherState.allowTaskbarInitialSplitSelection());
+ }
+
+ /**
* Updates the proper flag to change the state of the task bar.
*
* Note that this only updates the flag. {@link #applyState()} needs to be called separately.
@@ -373,11 +390,11 @@
}
}
- private boolean hasAnyFlag(int flagMask) {
+ private boolean hasAnyFlag(long flagMask) {
return hasAnyFlag(mState, flagMask);
}
- private boolean hasAnyFlag(int flags, int flagMask) {
+ private boolean hasAnyFlag(long flags, long flagMask) {
return (flags & flagMask) != 0;
}
@@ -646,11 +663,12 @@
* Returns if the current Launcher state has hotseat on top of other elemnets.
*/
public boolean isInHotseatOnTopStates() {
- return mLauncherState != LauncherState.ALL_APPS;
+ return mLauncherState != LauncherState.ALL_APPS
+ && !mLauncher.getWorkspace().isOverlayShown();
}
- boolean isInOverview() {
- return mLauncherState == LauncherState.OVERVIEW;
+ boolean isInOverviewUi() {
+ return mLauncherState.isRecentsViewVisible;
}
private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
@@ -760,19 +778,33 @@
@Override
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
boolean isInOverview = mLauncher.isInState(LauncherState.OVERVIEW);
- endGestureStateOverride(!isInOverview);
+ endGestureStateOverride(!isInOverview, true /*canceled*/);
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
- endGestureStateOverride(!controller.getFinishTargetIsLauncher());
+ endGestureStateOverride(!controller.getFinishTargetIsLauncher(), false /*canceled*/);
}
- private void endGestureStateOverride(boolean finishedToApp) {
+ /**
+ * Handles whatever cleanup is needed after the recents animation is completed.
+ * NOTE: If {@link #mSkipNextRecentsAnimEnd} is set and we're coming from a non-cancelled
+ * path, this will not call {@link #updateStateForUserFinishedToApp(boolean)}
+ *
+ * @param finishedToApp {@code true} if the recents animation finished to showing an app and
+ * not workspace or overview
+ * @param canceled {@code true} if the recents animation was canceled instead of finishing
+ * to completion
+ */
+ private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
mCallbacks.removeListener(this);
mTaskBarRecentsAnimationListener = null;
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
+ if (mSkipNextRecentsAnimEnd && !canceled) {
+ mSkipNextRecentsAnimEnd = false;
+ return;
+ }
updateStateForUserFinishedToApp(finishedToApp);
}
}
@@ -806,6 +838,7 @@
appendFlag(result, flags, FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE,
"was_active_while_awake");
appendFlag(result, flags, FLAG_DEVICE_LOCKED, "device_locked");
+ appendFlag(result, flags, FLAG_TASKBAR_HIDDEN, "taskbar_hidden");
return result.toString();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ecbc7e7..2a58db2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar;
import static android.content.Context.RECEIVER_NOT_EXPORTED;
-import static android.content.pm.PackageManager.FEATURE_PC;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -26,10 +25,10 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
+import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
@@ -43,7 +42,6 @@
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
-import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Handler;
@@ -60,9 +58,11 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
@@ -71,9 +71,9 @@
import com.android.quickstep.AllAppsActionManager;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.AssistUtils;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
@@ -111,7 +111,6 @@
private final Context mContext;
private final @Nullable Context mNavigationBarPanelContext;
- private final DeviceStateManager mDeviceStateManager;
private WindowManager mWindowManager;
private FrameLayout mTaskbarRootLayout;
private boolean mAddedWindow;
@@ -145,7 +144,7 @@
private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
- if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE
+ if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
| CHANGE_TASKBAR_PINNING)) != 0) {
recreateTaskbar();
}
@@ -180,8 +179,7 @@
}
};
- private final UnfoldTransitionProgressProvider.TransitionProgressListener
- mUnfoldTransitionProgressListener =
+ UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener =
new UnfoldTransitionProgressProvider.TransitionProgressListener() {
@Override
public void onTransitionStarted() {
@@ -210,20 +208,20 @@
}
};
- private final DeviceStateManager.FoldStateListener mFoldStateListener;
- private Boolean mFolded;
-
@SuppressLint("WrongConstant")
public TaskbarManager(
- TouchInteractionService service, AllAppsActionManager allAppsActionManager) {
+ Context context,
+ AllAppsActionManager allAppsActionManager,
+ TaskbarNavButtonCallbacks navCallbacks) {
+
Display display =
- service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
- mContext = service.createWindowContext(display,
+ context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+ mContext = context.createWindowContext(display,
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
null);
mAllAppsActionManager = allAppsActionManager;
mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
- ? service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
+ ? context.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
: null;
if (enableTaskbarNoRecreate()) {
mWindowManager = mContext.getSystemService(WindowManager.class);
@@ -240,31 +238,11 @@
}
};
}
- // Temporary solution to mitigate the visual jump from folding the device. Currently, the
- // screen turns on much earlier than we receive the onConfigurationChanged callback or
- // receiving the correct device profile. While the ideal the solution is to align turning
- // the screen on after onConfigurationChanged (by either delaying turning on the screen or
- // figuring out what is causing the delay in getting onConfigurationChanged callback), one
- // easy temporary mitigation is to dimming the bar so that the visual jump isn't as glaring.
- mFoldStateListener = new DeviceStateManager.FoldStateListener(mContext, folded -> {
- boolean firstTime = mFolded == null;
- if (mTaskbarActivityContext == null) {
- return;
- }
- if (!firstTime && mFolded.booleanValue() != folded) {
- mTaskbarActivityContext.cancelHideTaskbarWhenFolding();
- }
- mFolded = folded;
- if (folded && !firstTime) {
- mTaskbarActivityContext.hideTaskbarWhenFolding();
- } else {
- mTaskbarActivityContext.resetHideTaskbarWhenUnfolding();
- }
- });
- mDeviceStateManager = mContext.getSystemService(DeviceStateManager.class);
- mDeviceStateManager.registerCallback(MAIN_EXECUTOR, mFoldStateListener);
- mNavButtonController = new TaskbarNavButtonController(service,
- SystemUiProxy.INSTANCE.get(mContext), new Handler(),
+ mNavButtonController = new TaskbarNavButtonController(
+ context,
+ navCallbacks,
+ SystemUiProxy.INSTANCE.get(mContext),
+ new Handler(),
AssistUtils.newInstance(mContext));
mComponentCallbacks = new ComponentCallbacks() {
private Configuration mOldConfig = mContext.getResources().getConfiguration();
@@ -369,12 +347,12 @@
/**
* Toggles All Apps for Taskbar or Launcher depending on the current state.
- *
- * @param homeAllAppsIntent Intent used if Taskbar is not enabled or Launcher is resumed.
*/
- public void toggleAllApps(Intent homeAllAppsIntent) {
+ public void toggleAllApps() {
if (mTaskbarActivityContext == null || mTaskbarActivityContext.canToggleHomeAllApps()) {
- mContext.startActivity(homeAllAppsIntent);
+ // Home All Apps should be toggled from this class, because the controllers are not
+ // initialized when Taskbar is disabled (i.e. TaskbarActivityContext is null).
+ if (mActivity instanceof Launcher l) l.toggleAllAppsSearch();
} else {
mTaskbarActivityContext.toggleAllAppsSearch();
}
@@ -449,9 +427,6 @@
*/
private TaskbarUIController createTaskbarUIControllerForActivity(StatefulActivity activity) {
if (activity instanceof QuickstepLauncher) {
- if (mTaskbarActivityContext.getPackageManager().hasSystemFeature(FEATURE_PC)) {
- return new DesktopTaskbarUIController((QuickstepLauncher) activity);
- }
return new LauncherTaskbarUIController((QuickstepLauncher) activity);
}
if (activity instanceof RecentsActivity) {
@@ -473,7 +448,8 @@
LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
// All Apps action is unrelated to navbar unification, so we only need to check DP.
- mAllAppsActionManager.setTaskbarPresent(dp != null && dp.isTaskbarPresent);
+ final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
+ mAllAppsActionManager.setTaskbarPresent(isLargeScreenTaskbar);
destroyExistingTaskbar();
@@ -497,6 +473,7 @@
}
mSharedState.startTaskbarVariantIsTransient =
DisplayController.isTransientTaskbar(mTaskbarActivityContext);
+ mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
mTaskbarActivityContext.init(mSharedState);
if (mActivity != null) {
@@ -515,7 +492,7 @@
}
}
- public void onSystemUiFlagsChanged(int systemUiStateFlags) {
+ public void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags) {
if (DEBUG) {
Log.d(TAG, "SysUI flags changed: " + formatFlagChange(systemUiStateFlags,
mSharedState.sysuiStateFlags, QuickStepContract::getSystemUiStateString));
@@ -542,7 +519,7 @@
}
}
- private static boolean isTaskbarEnabled(DeviceProfile deviceProfile) {
+ private boolean isTaskbarEnabled(DeviceProfile deviceProfile) {
return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
}
@@ -619,7 +596,6 @@
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
mContext.unregisterComponentCallbacks(mComponentCallbacks);
mContext.unregisterReceiver(mShutdownReceiver);
- mDeviceStateManager.unregisterCallback(mFoldStateListener);
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
@@ -635,7 +611,8 @@
}
}
- private void addTaskbarRootViewToWindow() {
+ @VisibleForTesting
+ void addTaskbarRootViewToWindow() {
if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) {
mWindowManager.addView(mTaskbarRootLayout,
mTaskbarActivityContext.getWindowLayoutParams());
@@ -643,7 +620,8 @@
}
}
- private void removeTaskbarRootViewFromWindow() {
+ @VisibleForTesting
+ void removeTaskbarRootViewFromWindow() {
if (enableTaskbarNoRecreate() && mAddedWindow) {
mWindowManager.removeViewImmediate(mTaskbarRootLayout);
mAddedWindow = false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 14d46d1..2b0e169 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.window.flags.Flags.enableDesktopWindowingMode;
+import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
+
import android.util.SparseArray;
import android.view.View;
@@ -26,6 +29,7 @@
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.statehandlers.DesktopVisibilityController;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -33,6 +37,7 @@
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.RecentsModel;
import java.io.PrintWriter;
@@ -42,6 +47,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -62,6 +68,8 @@
// Used to defer any UI updates during the SUW unstash animation.
private boolean mDeferUpdatesForSUW;
private Runnable mDeferredUpdates;
+ private final DesktopVisibilityController.DesktopVisibilityListener mDesktopVisibilityListener =
+ visible -> updateRunningApps();
public TaskbarModelCallbacks(
TaskbarActivityContext context, TaskbarView container) {
@@ -71,8 +79,17 @@
public void init(TaskbarControllers controllers) {
mControllers = controllers;
- if (mControllers.taskbarRecentAppsController.isEnabled()) {
+ if (mControllers.taskbarRecentAppsController.getCanShowRunningApps()) {
RecentsModel.INSTANCE.get(mContext).registerRunningTasksListener(this);
+
+ if (shouldShowRunningAppsInDesktopMode()) {
+ DesktopVisibilityController desktopVisibilityController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ desktopVisibilityController.registerDesktopVisibilityListener(
+ mDesktopVisibilityListener);
+ }
+ }
}
}
@@ -81,6 +98,20 @@
*/
public void unregisterListeners() {
RecentsModel.INSTANCE.get(mContext).unregisterRunningTasksListener();
+
+ if (shouldShowRunningAppsInDesktopMode()) {
+ DesktopVisibilityController desktopVisibilityController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ desktopVisibilityController.unregisterDesktopVisibilityListener(
+ mDesktopVisibilityListener);
+ }
+ }
+ }
+
+ private boolean shouldShowRunningAppsInDesktopMode() {
+ // TODO(b/335401172): unify DesktopMode checks in Launcher
+ return enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps();
}
@Override
@@ -203,15 +234,26 @@
}
hotseatItemInfos = mControllers.taskbarRecentAppsController
.updateHotseatItemInfos(hotseatItemInfos);
+ Set<String> runningPackages = mControllers.taskbarRecentAppsController.getRunningApps();
+ Set<String> minimizedPackages = mControllers.taskbarRecentAppsController.getMinimizedApps();
if (mDeferUpdatesForSUW) {
ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
- mDeferredUpdates = () -> mContainer.updateHotseatItems(finalHotseatItemInfos);
+ mDeferredUpdates = () ->
+ commitHotseatItemUpdates(finalHotseatItemInfos, runningPackages,
+ minimizedPackages);
} else {
- mContainer.updateHotseatItems(hotseatItemInfos);
+ commitHotseatItemUpdates(hotseatItemInfos, runningPackages, minimizedPackages);
}
}
+ private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, Set<String> runningPackages,
+ Set<String> minimizedPackages) {
+ mContainer.updateHotseatItems(hotseatItemInfos);
+ mControllers.taskbarViewController.updateIconViewsRunningStates(runningPackages,
+ minimizedPackages);
+ }
+
/**
* This is used to defer UI updates after SUW builds the unstash animation.
* @param defer if true, defers updates to the UI
@@ -240,7 +282,7 @@
/** Call TaskbarRecentAppsController to update running apps with mHotseatItems. */
public void updateRunningApps() {
- mControllers.taskbarRecentAppsController.updateRunningApps(mHotseatItems);
+ mControllers.taskbarRecentAppsController.updateRunningApps();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 19293b5..d26a36d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -27,11 +27,11 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
@@ -44,15 +44,12 @@
import com.android.launcher3.R;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.AssistUtils;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -68,7 +65,7 @@
/** Allow some time in between the long press for back and recents. */
static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200;
static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100;
- private static final String TAG = TaskbarNavButtonController.class.getSimpleName();
+ private static final String TAG = "TaskbarNavButtonController";
private long mLastScreenPinLongPress;
private boolean mScreenPinned;
@@ -107,7 +104,8 @@
private static final int SCREEN_UNPIN_COMBO = BUTTON_BACK | BUTTON_RECENTS;
private int mLongPressedButtons = 0;
- private final TouchInteractionService mService;
+ private final Context mContext;
+ private final TaskbarNavButtonCallbacks mCallbacks;
private final SystemUiProxy mSystemUiProxy;
private final Handler mHandler;
private final AssistUtils mAssistUtils;
@@ -115,9 +113,14 @@
private final Runnable mResetLongPress = this::resetScreenUnpin;
- public TaskbarNavButtonController(TouchInteractionService service,
- SystemUiProxy systemUiProxy, Handler handler, AssistUtils assistUtils) {
- mService = service;
+ public TaskbarNavButtonController(
+ Context context,
+ TaskbarNavButtonCallbacks callbacks,
+ SystemUiProxy systemUiProxy,
+ Handler handler,
+ AssistUtils assistUtils) {
+ mContext = context;
+ mCallbacks = callbacks;
mSystemUiProxy = systemUiProxy;
mHandler = handler;
mAssistUtils = assistUtils;
@@ -254,7 +257,7 @@
mLastScreenPinLongPress = 0;
}
- public void updateSysuiFlags(int sysuiFlags) {
+ public void updateSysuiFlags(@SystemUiStateFlags long sysuiFlags) {
mScreenPinned = (sysuiFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
}
@@ -280,16 +283,7 @@
private void navigateHome() {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
-
- if (isDesktopModeSupported()) {
- DesktopVisibilityController desktopVisibilityController =
- LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
- if (desktopVisibilityController != null) {
- desktopVisibilityController.onHomeActionTriggered();
- }
- }
-
- mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
+ mCallbacks.onNavigateHome();
}
private void navigateToOverview() {
@@ -298,7 +292,7 @@
}
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+ mCallbacks.onToggleOverview();
}
private void executeBack() {
@@ -313,7 +307,7 @@
if (longClick) {
mSystemUiProxy.notifyAccessibilityButtonLongClicked();
} else {
- mSystemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId());
+ mSystemUiProxy.notifyAccessibilityButtonClicked(mContext.getDisplayId());
}
}
@@ -336,4 +330,13 @@
private void showNotifications() {
mSystemUiProxy.toggleNotificationPanel();
}
+
+ /** Callbacks for navigation buttons on Taskbar. */
+ public interface TaskbarNavButtonCallbacks {
+ /** Callback invoked when the home button is pressed. */
+ default void onNavigateHome() {}
+
+ /** Callback invoked when the overview button is pressed. */
+ default void onToggleOverview() {}
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
index 2f2d636..6c9cc64 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
@@ -23,6 +23,7 @@
import com.android.app.animation.Interpolators
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
+import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_PINNED
@@ -31,8 +32,10 @@
import java.io.PrintWriter
/** Controls taskbar pinning through a popup view. */
-class TaskbarPinningController(private val context: TaskbarActivityContext) :
- TaskbarControllers.LoggableTaskbarController {
+class TaskbarPinningController(
+ private val context: TaskbarActivityContext,
+ private val isInDesktopModeProvider: () -> Boolean,
+) : TaskbarControllers.LoggableTaskbarController {
private lateinit var controllers: TaskbarControllers
private lateinit var taskbarSharedState: TaskbarSharedState
@@ -54,14 +57,22 @@
if (!didPreferenceChange) {
return
}
+ val shouldPinTaskbar =
+ if (isInDesktopModeProvider()) {
+ !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
+ } else {
+ !launcherPrefs.get(TASKBAR_PINNING)
+ }
+
val animateToValue =
- if (!launcherPrefs.get(TASKBAR_PINNING)) {
+ if (shouldPinTaskbar) {
statsLogManager.logger().log(LAUNCHER_TASKBAR_PINNED)
PINNING_PERSISTENT
} else {
statsLogManager.logger().log(LAUNCHER_TASKBAR_UNPINNED)
PINNING_TRANSIENT
}
+
taskbarSharedState.taskbarWasPinned = animateToValue == PINNING_TRANSIENT
animateTaskbarPinning(animateToValue)
}
@@ -123,13 +134,24 @@
@VisibleForTesting
fun recreateTaskbarAndUpdatePinningValue() {
updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(false)
- launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
+ if (isInDesktopModeProvider()) {
+ launcherPrefs.put(
+ TASKBAR_PINNING_IN_DESKTOP_MODE,
+ !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
+ )
+ } else {
+ launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
+ }
}
override fun dumpLogs(prefix: String, pw: PrintWriter) {
pw.println(prefix + "TaskbarPinningController:")
pw.println("$prefix\tisAnimatingTaskbarPinning=$isAnimatingTaskbarPinning")
pw.println("$prefix\tTASKBAR_PINNING shared pref =" + launcherPrefs.get(TASKBAR_PINNING))
+ pw.println(
+ "$prefix\tTASKBAR_PINNING_IN_DESKTOP_MODE shared pref in desktop mode =" +
+ launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
+ )
}
companion object {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index ca192c8..2730be1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -116,9 +116,9 @@
}
} else if (info instanceof FolderInfo && v instanceof FolderIcon) {
FolderInfo fi = (FolderInfo) info;
- if (fi.contents.stream().anyMatch(matcher)) {
+ if (fi.anyMatch(matcher)) {
FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (WorkspaceItemInfo si : fi.contents) {
+ for (ItemInfo si : fi.getContents()) {
folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
}
((FolderIcon) v).setDotInfo(folderDotInfo);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
deleted file mode 100644
index 8445cff..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import android.util.SparseArray;
-
-import androidx.annotation.CallSuper;
-
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-
-/**
- * Base class for providing recent apps functionality
- */
-public class TaskbarRecentAppsController {
-
- public static final TaskbarRecentAppsController DEFAULT = new TaskbarRecentAppsController();
-
- // Initialized in init.
- protected TaskbarControllers mControllers;
-
- @CallSuper
- protected void init(TaskbarControllers taskbarControllers) {
- mControllers = taskbarControllers;
- }
-
- @CallSuper
- protected void onDestroy() {
- mControllers = null;
- }
-
- /** Stores the current {@link AppInfo} instances, no-op except in desktop environment. */
- protected void setApps(AppInfo[] apps) { }
-
- /**
- * Indicates whether recent apps functionality is enabled, should return false except in
- * desktop environment.
- */
- protected boolean isEnabled() {
- return false;
- }
-
- /** Called to update hotseatItems, no-op except in desktop environment. */
- protected ItemInfo[] updateHotseatItemInfos(ItemInfo[] hotseatItems) {
- return hotseatItems;
- }
-
- /** Called to update the list of currently running apps, no-op except in desktop environment. */
- protected void updateRunningApps(SparseArray<ItemInfo> hotseatItems) { }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
new file mode 100644
index 0000000..b1fc9cc
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import androidx.annotation.VisibleForTesting
+import com.android.launcher3.Flags.enableRecentsInTaskbar
+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.statehandlers.DesktopVisibilityController
+import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
+import com.android.quickstep.RecentsModel
+import com.android.window.flags.Flags.enableDesktopWindowingMode
+import com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps
+import java.io.PrintWriter
+
+/**
+ * Provides recent apps functionality, when the Taskbar Recent Apps section is enabled. Behavior:
+ * - When in Fullscreen mode: show the N most recent Tasks
+ * - When in Desktop Mode: show the currently running (open) Tasks
+ */
+class TaskbarRecentAppsController(
+ private val recentsModel: RecentsModel,
+ // Pass a provider here instead of the actual DesktopVisibilityController instance since that
+ // instance might not be available when this constructor is called.
+ private val desktopVisibilityControllerProvider: () -> DesktopVisibilityController?,
+) : LoggableTaskbarController {
+
+ // TODO(b/335401172): unify DesktopMode checks in Launcher.
+ val canShowRunningApps =
+ enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps()
+
+ // TODO(b/343532825): Add a setting to disable Recents even when the flag is on.
+ var isEnabled: Boolean = enableRecentsInTaskbar() || canShowRunningApps
+ @VisibleForTesting
+ set(isEnabledFromTest){
+ field = isEnabledFromTest
+ }
+
+ // Initialized in init.
+ private lateinit var controllers: TaskbarControllers
+
+ private var apps: Array<AppInfo>? = null
+ private var allRunningDesktopAppInfos: List<AppInfo>? = null
+ private var allMinimizedDesktopAppInfos: List<AppInfo>? = null
+
+ private val desktopVisibilityController: DesktopVisibilityController?
+ get() = desktopVisibilityControllerProvider()
+
+ private val isInDesktopMode: Boolean
+ get() = desktopVisibilityController?.areDesktopTasksVisible() ?: false
+
+ val runningApps: Set<String>
+ get() {
+ if (!isEnabled || !isInDesktopMode) {
+ return emptySet()
+ }
+ return allRunningDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet()
+ }
+
+ val minimizedApps: Set<String>
+ get() {
+ if (!isInDesktopMode) {
+ return emptySet()
+ }
+ return allMinimizedDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet()
+ ?: emptySet()
+ }
+
+ fun init(taskbarControllers: TaskbarControllers) {
+ controllers = taskbarControllers
+ }
+
+ fun onDestroy() {
+ apps = null
+ }
+
+ /** Stores the current [AppInfo] instances, no-op except in desktop environment. */
+ fun setApps(apps: Array<AppInfo>?) {
+ this.apps = apps
+ }
+
+ /** Called to update hotseatItems, in order to de-dupe them from Recent/Running tasks later. */
+ // TODO(next CL): add new section of Tasks instead of changing Hotseat items
+ fun updateHotseatItemInfos(hotseatItems: Array<ItemInfo?>): Array<ItemInfo?> {
+ if (!isEnabled || !isInDesktopMode) {
+ return hotseatItems
+ }
+ val newHotseatItemInfos =
+ hotseatItems
+ .filterNotNull()
+ // Ignore predicted apps - we show running apps instead
+ .filter { itemInfo -> !itemInfo.isPredictedItem }
+ .toMutableList()
+ val runningDesktopAppInfos =
+ allRunningDesktopAppInfos?.let {
+ getRunningDesktopAppInfosExceptHotseatApps(it, newHotseatItemInfos.toList())
+ }
+ if (runningDesktopAppInfos != null) {
+ newHotseatItemInfos.addAll(runningDesktopAppInfos)
+ }
+ return newHotseatItemInfos.toTypedArray()
+ }
+
+ private fun getRunningDesktopAppInfosExceptHotseatApps(
+ allRunningDesktopAppInfos: List<AppInfo>,
+ hotseatItems: List<ItemInfo>
+ ): List<ItemInfo> {
+ val hotseatPackages = hotseatItems.map { it.targetPackage }
+ return allRunningDesktopAppInfos
+ .filter { appInfo -> !hotseatPackages.contains(appInfo.targetPackage) }
+ .map { WorkspaceItemInfo(it) }
+ }
+
+ private fun getDesktopRunningTasks(): List<RunningTaskInfo> =
+ recentsModel.runningTasks.filter { taskInfo: RunningTaskInfo ->
+ taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
+ }
+
+ // TODO(b/335398876) fetch app icons from Tasks instead of AppInfos
+ private fun getAppInfosFromRunningTasks(tasks: List<RunningTaskInfo>): List<AppInfo> {
+ // Early return if apps is empty, since we then have no AppInfo to compare to
+ if (apps == null) {
+ return emptyList()
+ }
+ val packageNames = tasks.map { it.realActivity?.packageName }.distinct().filterNotNull()
+ return packageNames
+ .map { packageName -> apps?.find { app -> packageName == app.targetPackage } }
+ .filterNotNull()
+ }
+
+ /** Called to update the list of currently running apps, no-op except in desktop environment. */
+ fun updateRunningApps() {
+ if (!isEnabled || !isInDesktopMode) {
+ return controllers.taskbarViewController.commitRunningAppsToUI()
+ }
+ val runningTasks = getDesktopRunningTasks()
+ val runningAppInfo = getAppInfosFromRunningTasks(runningTasks)
+ allRunningDesktopAppInfos = runningAppInfo
+ updateMinimizedApps(runningTasks, runningAppInfo)
+ controllers.taskbarViewController.commitRunningAppsToUI()
+ }
+
+ private fun updateMinimizedApps(
+ runningTasks: List<RunningTaskInfo>,
+ runningAppInfo: List<AppInfo>,
+ ) {
+ val allRunningAppTasks =
+ runningAppInfo
+ .mapNotNull { appInfo -> appInfo.targetPackage?.let { appInfo to it } }
+ .associate { (appInfo, targetPackage) ->
+ appInfo to
+ runningTasks
+ .filter { it.realActivity?.packageName == targetPackage }
+ .map { it.taskId }
+ }
+ val minimizedTaskIds = runningTasks.associate { it.taskId to !it.isVisible }
+ allMinimizedDesktopAppInfos =
+ allRunningAppTasks
+ .filterValues { taskIds -> taskIds.all { minimizedTaskIds[it] ?: false } }
+ .keys
+ .toList()
+ }
+
+ override fun dumpLogs(prefix: String, pw: PrintWriter) {
+ pw.println("$prefix TaskbarRecentAppsController:")
+ pw.println("$prefix\tisEnabled=$isEnabled")
+ pw.println("$prefix\tcanShowRunningApps=$canShowRunningApps")
+ // TODO(next CL): add more logs
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
index 712374d..48d2bc2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -30,6 +30,7 @@
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.PrintWriter;
@@ -45,7 +46,8 @@
private final TaskbarActivityContext mActivity;
private final TaskbarScrimView mScrimView;
private boolean mTaskbarVisible;
- private int mSysUiStateFlags;
+ @SystemUiStateFlags
+ private long mSysUiStateFlags;
// Alpha property for the scrim.
private final AnimatedFloat mScrimAlpha = new AnimatedFloat(this::updateScrimAlpha);
@@ -82,7 +84,7 @@
/**
* Updates the scrim state based on the flags.
*/
- public void updateStateForSysuiFlags(int stateFlags, boolean skipAnim) {
+ public void updateStateForSysuiFlags(@SystemUiStateFlags long stateFlags, boolean skipAnim) {
if (isBubbleBarEnabled() && DisplayController.isTransientTaskbar(mActivity)) {
// These scrims aren't used if bubble bar & transient taskbar are active.
return;
@@ -101,14 +103,20 @@
}
private float getScrimAlpha() {
+ final boolean isPersistentTaskBarVisible =
+ mTaskbarVisible && !DisplayController.isTransientTaskbar(mScrimView.getContext());
final boolean manageMenuExpanded =
(mSysUiStateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
- return manageMenuExpanded
- // When manage menu shows there's the first scrim and second scrim so figure out
- // what the total transparency would be.
- ? (BUBBLE_EXPANDED_SCRIM_ALPHA + (BUBBLE_EXPANDED_SCRIM_ALPHA
- * (1 - BUBBLE_EXPANDED_SCRIM_ALPHA)))
- : shouldShowScrim() ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0;
+ if (isPersistentTaskBarVisible && manageMenuExpanded) {
+ // When manage menu shows for persistent task bar there's the first scrim and second
+ // scrim so figure out what the total transparency would be.
+ return BUBBLE_EXPANDED_SCRIM_ALPHA
+ + (BUBBLE_EXPANDED_SCRIM_ALPHA * (1 - BUBBLE_EXPANDED_SCRIM_ALPHA));
+ } else if (shouldShowScrim()) {
+ return BUBBLE_EXPANDED_SCRIM_ALPHA;
+ } else {
+ return 0;
+ }
}
private void showScrim(boolean showScrim, float alpha, boolean skipAnim) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index e2c71bf..edaeb63 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -29,6 +29,8 @@
import android.os.IBinder;
import android.view.InsetsFrameProvider;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+
/**
* State shared across different taskbar instance
*/
@@ -39,7 +41,8 @@
private static int INDEX_RIGHT = 1;
// TaskbarManager#onSystemUiFlagsChanged
- public int sysuiStateFlags;
+ @SystemUiStateFlags
+ public long sysuiStateFlags;
// TaskbarManager#disableNavBarElements()
public int disableNavBarDisplayId;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 3d58464..7ff887c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -22,10 +22,10 @@
import static com.android.app.animation.Interpolators.INSTANT;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.internal.jank.InteractionJankMonitor.Configuration;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
-import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
@@ -35,11 +35,11 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
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;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
import android.app.RemoteAction;
import android.graphics.drawable.Icon;
import android.os.SystemClock;
@@ -66,25 +66,26 @@
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.SystemUiFlagUtils;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.StringJoiner;
-import java.util.function.IntPredicate;
+import java.util.function.LongPredicate;
/**
* Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
* create a cohesive animation between stashed/unstashed states.
*/
public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController {
- private static final String TAG = TaskbarStashController.class.getSimpleName();
+ private static final String TAG = "TaskbarStashController";
private static final boolean DEBUG = false;
public static final int FLAG_IN_APP = 1 << 0;
public static final int FLAG_STASHED_IN_APP_SYSUI = 1 << 1; // shade open, ...
public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 2; // setup wizard and AllSetActivity
- public static final int FLAG_STASHED_IN_APP_IME = 1 << 3; // IME is visible
+ public static final int FLAG_STASHED_IME = 1 << 3; // IME is visible
public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 4;
public static final int FLAG_STASHED_IN_TASKBAR_ALL_APPS = 1 << 5; // All apps is visible.
public static final int FLAG_IN_SETUP = 1 << 6; // In the Setup Wizard
@@ -99,17 +100,18 @@
// If we're in an app and any of these flags are enabled, taskbar should be stashed.
private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_SYSUI
- | FLAG_STASHED_IN_APP_SETUP | FLAG_STASHED_IN_APP_IME
- | FLAG_STASHED_IN_TASKBAR_ALL_APPS | FLAG_STASHED_SMALL_SCREEN
- | FLAG_STASHED_IN_APP_AUTO;
+ | FLAG_STASHED_IN_APP_SETUP | FLAG_STASHED_IN_TASKBAR_ALL_APPS
+ | FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO | FLAG_STASHED_IME;
+
+ // If we're in overview and any of these flags are enabled, taskbar should be stashed.
+ private static final int FLAGS_STASHED_IN_OVERVIEW = FLAG_STASHED_IME;
// If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
// height. This way the reported insets are consistent even during transitions out of the app.
// Currently any flag that causes us to stash in an app is included, except for IME or All Apps
// since those cover the underlying app anyway and thus the app shouldn't change insets.
private static final int FLAGS_REPORT_STASHED_INSETS_TO_APP = FLAGS_STASHED_IN_APP
- & ~FLAG_STASHED_IN_APP_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS
- & ~FLAG_STASHED_IN_APP_SYSUI;
+ & ~FLAG_STASHED_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS & ~FLAG_STASHED_IN_APP_SYSUI;
// If any of these flags are enabled, the taskbar must be stashed.
private static final int FLAGS_FORCE_STASHED = FLAG_STASHED_SYSUI | FLAG_STASHED_DEVICE_LOCKED
@@ -231,7 +233,7 @@
/** Whether we are currently visually stashed (might change based on launcher state). */
private boolean mIsStashed = false;
- private int mState;
+ private long mState;
private @Nullable AnimatorSet mAnimator;
private boolean mIsSystemGestureInProgress;
@@ -241,13 +243,22 @@
private final Alarm mTimeoutAlarm = new Alarm();
private boolean mEnableBlockingTimeoutDuringTests = false;
+ private Animator mTaskbarBackgroundAlphaAnimator;
+ private long mTaskbarBackgroundDuration;
+ private boolean mIsGoingHome;
+
// Evaluate whether the handle should be stashed
- private final IntPredicate mIsStashedPredicate = flags -> {
+ private final LongPredicate mIsStashedPredicate = flags -> {
boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
+ boolean inOverview = hasAnyFlag(flags, FLAG_IN_OVERVIEW);
+ boolean stashedInOverview = hasAnyFlag(flags, FLAGS_STASHED_IN_OVERVIEW);
boolean forceStashed = hasAnyFlag(flags, FLAGS_FORCE_STASHED);
- return (inApp && stashedInApp) || (!inApp && stashedLauncherState) || forceStashed;
+ return (inApp && stashedInApp)
+ || (!inApp && stashedLauncherState)
+ || (inOverview && stashedInOverview)
+ || forceStashed;
};
private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
mIsStashedPredicate);
@@ -260,6 +271,8 @@
mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
+ mTaskbarBackgroundDuration =
+ activity.getResources().getInteger(R.integer.taskbar_background_duration);
if (mActivity.isPhoneMode()) {
mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
R.dimen.taskbar_phone_size);
@@ -321,8 +334,7 @@
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isStashedInAppAuto);
updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
updateStateForFlag(FLAG_IN_SETUP, isInSetup);
- updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, mActivity.isPhoneMode()
- && !mActivity.isThreeButtonNav());
+ updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, mActivity.isPhoneGestureNavMode());
// For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
// us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
updateStateForFlag(FLAG_IN_APP, true);
@@ -391,11 +403,11 @@
return (hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing());
}
- private boolean hasAnyFlag(int flagMask) {
+ private boolean hasAnyFlag(long flagMask) {
return hasAnyFlag(mState, flagMask);
}
- private boolean hasAnyFlag(int flags, int flagMask) {
+ private boolean hasAnyFlag(long flags, long flagMask) {
return (flags & flagMask) != 0;
}
@@ -427,14 +439,15 @@
* @see android.view.WindowInsets.Type#systemBars()
*/
public int getContentHeightToReportToApps() {
- if ((mActivity.isPhoneMode() && !mActivity.isThreeButtonNav())
- || DisplayController.isTransientTaskbar(mActivity)) {
+ if (mActivity.isUserSetupComplete() && (mActivity.isPhoneGestureNavMode()
+ || DisplayController.isTransientTaskbar(mActivity))) {
return getStashedHeight();
}
if (supportsVisualStashing() && hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
DeviceProfile dp = mActivity.getDeviceProfile();
- if (hasAnyFlag(FLAG_STASHED_IN_APP_SETUP) && dp.isTaskbarPresent) {
+ if (hasAnyFlag(FLAG_STASHED_IN_APP_SETUP) && (dp.isTaskbarPresent
+ || mActivity.isPhoneGestureNavMode())) {
// We always show the back button in SUW but in portrait the SUW layout may not
// be wide enough to support overlapping the nav bar with its content.
// We're sending different res values in portrait vs landscape
@@ -549,11 +562,12 @@
* sub-animations are properly coordinated. This duration should not
* actually be used since this animation tracks a swipe progress.
*/
- protected void addUnstashToHotseatAnimation(AnimatorSet animation, int placeholderDuration) {
+ protected void addUnstashToHotseatAnimationFromSuw(AnimatorSet animation,
+ int placeholderDuration) {
// Defer any UI updates now to avoid the UI becoming stale when the animation plays.
mControllers.taskbarViewController.setDeferUpdatesForSUW(true);
createAnimToIsStashed(
- /* isStashed= */ false,
+ /* isStashed= */ mActivity.isPhoneMode(),
placeholderDuration,
TRANSITION_UNSTASH_SUW_MANUAL,
/* jankTag= */ "SUW_MANUAL");
@@ -598,7 +612,8 @@
? stashTranslation : 0)
.setDuration(duration));
mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
- hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
+ (hasAnyFlag(FLAG_STASHED_IME) && isStashed) ? 0 : 1).setDuration(
+ duration));
mAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
mAnimator = null;
mIsStashed = isStashed;
@@ -752,9 +767,16 @@
backgroundAndHandleAlphaStartDelay,
backgroundAndHandleAlphaDuration, LINEAR);
- play(as, mTaskbarBackgroundAlphaForStash.animateToValue(backgroundAlphaTarget),
- backgroundAndHandleAlphaStartDelay,
- backgroundAndHandleAlphaDuration, LINEAR);
+ if (enableScalingRevealHomeAnimation() && !isStashed) {
+ play(as, getTaskbarBackgroundAnimatorWhenNotGoingHome(duration),
+ 0, 0, LINEAR);
+ as.addListener(AnimatorListeners.forEndCallback(
+ () -> mTaskbarBackgroundAlphaForStash.setValue(backgroundAlphaTarget)));
+ } else {
+ play(as, mTaskbarBackgroundAlphaForStash.animateToValue(backgroundAlphaTarget),
+ backgroundAndHandleAlphaStartDelay,
+ backgroundAndHandleAlphaDuration, LINEAR);
+ }
// The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE transitions.
AnimatorSet skippable = as;
@@ -797,6 +819,62 @@
.setDuration(isStashed ? duration / 2 : duration));
}
+ private Animator getTaskbarBackgroundAnimatorWhenNotGoingHome(long duration) {
+ ValueAnimator a = ValueAnimator.ofFloat(0, 1);
+ a.setDuration(duration);
+ a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ // This value is arbitrary.
+ private static final float ANIMATED_FRACTION_THRESHOLD = 0.25f;
+ private boolean mTaskbarBgAlphaAnimationStarted = false;
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ if (mIsGoingHome) {
+ mTaskbarBgAlphaAnimationStarted = true;
+ }
+ if (mTaskbarBgAlphaAnimationStarted) {
+ return;
+ }
+
+ if (valueAnimator.getAnimatedFraction() >= ANIMATED_FRACTION_THRESHOLD) {
+ if (!mIsGoingHome) {
+ playTaskbarBackgroundAlphaAnimation();
+ setUserIsGoingHome(false);
+ mTaskbarBgAlphaAnimationStarted = true;
+ }
+ }
+ }
+ });
+ return a;
+ }
+
+ /**
+ * Sets whether the user is going home based on the current gesture.
+ */
+ public void setUserIsGoingHome(boolean isGoingHome) {
+ mIsGoingHome = isGoingHome;
+ }
+
+ /**
+ * Plays the taskbar background alpha animation if one is not currently playing.
+ */
+ public void playTaskbarBackgroundAlphaAnimation() {
+ if (mTaskbarBackgroundAlphaAnimator != null
+ && mTaskbarBackgroundAlphaAnimator.isRunning()) {
+ return;
+ }
+ mTaskbarBackgroundAlphaAnimator = mTaskbarBackgroundAlphaForStash
+ .animateToValue(1f)
+ .setDuration(mTaskbarBackgroundDuration);
+ mTaskbarBackgroundAlphaAnimator.setInterpolator(LINEAR);
+ mTaskbarBackgroundAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTaskbarBackgroundAlphaAnimator = null;
+ }
+ });
+ mTaskbarBackgroundAlphaAnimator.start();
+ }
+
private static void play(AnimatorSet as, @Nullable Animator a, long startDelay, long duration,
Interpolator interpolator) {
if (a == null) {
@@ -890,24 +968,18 @@
}
/**
- * Should be called when a system gesture starts and settles, so we can defer updating
- * FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
+ * Should be called when a system gesture starts and settles, so we can remove
+ * FLAG_STASHED_IN_APP_IME while the gesture is in progress.
*/
public void setSystemGestureInProgress(boolean inProgress) {
mIsSystemGestureInProgress = inProgress;
- if (mIsSystemGestureInProgress) {
- return;
- }
-
- // Only update the following flags when system gesture is not in progress.
- updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
setStashedImeState();
}
private void setStashedImeState() {
boolean shouldStashForIme = shouldStashForIme();
- if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != shouldStashForIme) {
- updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme);
+ if (hasAnyFlag(FLAG_STASHED_IME) != shouldStashForIme) {
+ updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme);
applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
} else {
applyState(mControllers.taskbarOverlayController.getCloseDuration());
@@ -936,7 +1008,7 @@
}
/** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
- public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
+ public void updateStateForSysuiFlags(long systemUiStateFlags, boolean skipAnim) {
long animDuration = TASKBAR_STASH_DURATION;
long startDelay = 0;
@@ -948,16 +1020,12 @@
&& DisplayController.isTransientTaskbar(mActivity);
updateStateForFlag(FLAG_STASHED_SYSUI,
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING) || stashForBubbles);
- boolean isLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED)
- && !hasAnyFlag(systemUiStateFlags, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY);
- updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, isLocked);
+ updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED,
+ SystemUiFlagUtils.isLocked(systemUiStateFlags));
- // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
-
- if (!mIsSystemGestureInProgress) {
- updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme());
+ if (updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme())) {
animDuration = TASKBAR_STASH_DURATION_FOR_IME;
startDelay = getTaskbarStashStartDelayForIme();
}
@@ -970,7 +1038,8 @@
*
* <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.
- * <p>Do not stash if hardware keyboard is attached and taskbar is pinned and IME is docked
+ * <p>Do not stash if hardware keyboard is attached and taskbar is pinned and IME is docked.
+ * <p>Do not stash if a system gesture is started.
*/
private boolean shouldStashForIme() {
if (DisplayController.isTransientTaskbar(mActivity)) {
@@ -992,7 +1061,12 @@
DesktopVisibilityController visibilityController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
if (visibilityController != null && mActivity.isHardwareKeyboard()
- && mActivity.isThreeButtonNav() && visibilityController.areFreeformTasksVisible()) {
+ && mActivity.isThreeButtonNav() && visibilityController.areDesktopTasksVisible()) {
+ return false;
+ }
+
+ // Do not stash if a gesture started.
+ if (mIsSystemGestureInProgress) {
return false;
}
@@ -1007,13 +1081,16 @@
* @param flag The flag to update.
* @param enabled Whether to enable the flag: True will cause the task bar to be stashed /
* unstashed.
+ * @return Whether the flag state changed.
*/
- public void updateStateForFlag(int flag, boolean enabled) {
+ public boolean updateStateForFlag(long flag, boolean enabled) {
+ long oldState = mState;
if (enabled) {
mState |= flag;
} else {
mState &= ~flag;
}
+ return mState != oldState;
}
/**
@@ -1021,7 +1098,7 @@
*
* @param changedFlags The flags that have changed.
*/
- private void onStateChangeApplied(int changedFlags) {
+ private void onStateChangeApplied(long changedFlags) {
if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP)) {
mControllers.uiController.onStashedInAppChanged();
}
@@ -1152,31 +1229,32 @@
pw.println(prefix + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing);
}
- private static String getStateString(int flags) {
+ private static String getStateString(long flags) {
StringJoiner sj = new StringJoiner("|");
appendFlag(sj, flags, FLAGS_IN_APP, "FLAG_IN_APP");
appendFlag(sj, flags, FLAG_STASHED_IN_APP_SYSUI, "FLAG_STASHED_IN_APP_SYSUI");
appendFlag(sj, flags, FLAG_STASHED_IN_APP_SETUP, "FLAG_STASHED_IN_APP_SETUP");
- appendFlag(sj, flags, FLAG_STASHED_IN_APP_IME, "FLAG_STASHED_IN_APP_IME");
+ appendFlag(sj, flags, FLAG_STASHED_IME, "FLAG_STASHED_IN_APP_IME");
appendFlag(sj, flags, FLAG_IN_STASHED_LAUNCHER_STATE, "FLAG_IN_STASHED_LAUNCHER_STATE");
appendFlag(sj, flags, FLAG_STASHED_IN_TASKBAR_ALL_APPS, "FLAG_STASHED_IN_TASKBAR_ALL_APPS");
appendFlag(sj, flags, FLAG_IN_SETUP, "FLAG_IN_SETUP");
appendFlag(sj, flags, FLAG_STASHED_IN_APP_AUTO, "FLAG_STASHED_IN_APP_AUTO");
appendFlag(sj, flags, FLAG_STASHED_SYSUI, "FLAG_STASHED_SYSUI");
appendFlag(sj, flags, FLAG_STASHED_DEVICE_LOCKED, "FLAG_STASHED_DEVICE_LOCKED");
+ appendFlag(sj, flags, FLAG_IN_OVERVIEW, "FLAG_IN_OVERVIEW");
return sj.toString();
}
private class StatePropertyHolder {
- private final IntPredicate mStashCondition;
+ private final LongPredicate mStashCondition;
private boolean mIsStashed;
private @StashAnimation int mLastStartedTransitionType = TRANSITION_DEFAULT;
- private int mPrevFlags;
+ private long mPrevFlags;
private long mLastUnlockTransitionTimeout = 0;
- StatePropertyHolder(IntPredicate stashCondition) {
+ StatePropertyHolder(LongPredicate stashCondition) {
mStashCondition = stashCondition;
}
@@ -1189,7 +1267,7 @@
* @return mAnimator if mIsStashed changed, or {@code null} otherwise.
*/
@Nullable
- public Animator createSetStateAnimator(int flags, long duration) {
+ public Animator createSetStateAnimator(long flags, long duration) {
boolean isStashed = mStashCondition.test(flags);
if (DEBUG) {
@@ -1201,7 +1279,7 @@
+ ", mIsStashed: " + mIsStashed);
}
- int changedFlags = mPrevFlags ^ flags;
+ long changedFlags = mPrevFlags ^ flags;
if (mPrevFlags != flags) {
onStateChangeApplied(changedFlags);
mPrevFlags = flags;
@@ -1248,7 +1326,7 @@
}
/** Calculates the tag for CUJ_TASKBAR_EXPAND and CUJ_TASKBAR_COLLAPSE jank traces. */
- private String computeTaskbarJankMonitorTag(int changedFlags) {
+ private String computeTaskbarJankMonitorTag(long changedFlags) {
if (hasAnyFlag(changedFlags, FLAG_IN_APP)) {
// moving in or out of the app
if (hasAnyFlag(FLAG_IN_APP)) {
@@ -1268,7 +1346,7 @@
return "";
}
- private @StashAnimation int computeTransitionType(int changedFlags) {
+ private @StashAnimation int computeTransitionType(long changedFlags) {
boolean hotseatHiddenDuringAppLaunch =
!mControllers.uiController.isHotseatIconOnTopWhenAligned()
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 109400e..593285f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -44,8 +44,9 @@
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.PrintWriter;
import java.util.Collections;
@@ -55,7 +56,6 @@
* Base class for providing different taskbar UI
*/
public class TaskbarUIController {
-
public static final TaskbarUIController DEFAULT = new TaskbarUIController();
// Initialized in init.
@@ -91,6 +91,10 @@
*/
protected void onIconLayoutBoundsChanged() { }
+ protected String getTaskbarUIControllerName() {
+ return "TaskbarUIController";
+ }
+
/** Called when an icon is launched. */
@CallSuper
public void onTaskbarIconLaunched(ItemInfo item) {
@@ -137,7 +141,7 @@
/**
* SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values.
*/
- public void updateStateForSysuiFlags(int sysuiFlags) {
+ public void updateStateForSysuiFlags(@SystemUiStateFlags long sysuiFlags) {
}
/**
@@ -193,7 +197,7 @@
}
/** Returns {@code true} if Taskbar is currently within overview. */
- protected boolean isInOverview() {
+ protected boolean isInOverviewUi() {
return false;
}
@@ -207,7 +211,7 @@
pw.println(String.format(
"%sTaskbarUIController: using an instance of %s",
prefix,
- getClass().getSimpleName()));
+ getTaskbarUIControllerName()));
}
/**
@@ -259,14 +263,14 @@
if (foundTaskView != null) {
// There is already a running app of this type, use that as second app.
// Get index of task (0 or 1), in case it's a GroupedTaskView
- TaskIdAttributeContainer taskAttributes =
- foundTaskView.getTaskAttributesById(foundTask.key.id);
+ TaskContainer taskContainer =
+ foundTaskView.getTaskContainerById(foundTask.key.id);
recents.confirmSplitSelect(
foundTaskView,
foundTask,
- taskAttributes.getIconView().getDrawable(),
- taskAttributes.getThumbnailView(),
- taskAttributes.getThumbnailView().getThumbnail(),
+ taskContainer.getIconView().getDrawable(),
+ taskContainer.getThumbnailViewDeprecated(),
+ taskContainer.getThumbnailViewDeprecated().getThumbnail(),
null /* intent */,
null /* user */,
info);
@@ -399,4 +403,19 @@
mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, !isVisible);
mControllers.taskbarStashController.applyState();
}
+
+ /**
+ * Request for UI controller to ignore animations for the next callback for the end of recents
+ * animation
+ */
+ public void setSkipNextRecentsAnimEnd() {
+ // Overridden
+ }
+
+ /**
+ * Sets whether the user is going home based on the current gesture.
+ */
+ public void setUserIsGoingHome(boolean isGoingHome) {
+ mControllers.taskbarStashController.setUserIsGoingHome(isGoingHome);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 367bf6c..570221c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,15 +15,16 @@
*/
package com.android.launcher3.taskbar;
-import static android.content.pm.PackageManager.FEATURE_PC;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+import static com.android.launcher3.BubbleTextView.DISPLAY_TASKBAR;
import static com.android.launcher3.Flags.enableCursorHoverStates;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
import android.content.res.Resources;
@@ -31,10 +32,12 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
+import android.view.DisplayCutout;
import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
@@ -52,6 +55,8 @@
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
+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.WorkspaceItemInfo;
@@ -60,6 +65,8 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.IconButtonView;
+import com.android.quickstep.DeviceConfigWrapper;
+import com.android.quickstep.util.AssistStateManager;
import java.util.function.Predicate;
@@ -68,8 +75,6 @@
*/
public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable,
DeviceProfile.OnDeviceProfileChangeListener {
- private static final String TAG = TaskbarView.class.getSimpleName();
-
private static final Rect sTmpRect = new Rect();
private final int[] mTempOutLocation = new int[2];
@@ -92,6 +97,9 @@
// Only non-null when device supports having an All Apps button.
private @Nullable IconButtonView mAllAppsButton;
+ private Runnable mAllAppsTouchRunnable;
+ private long mAllAppsButtonTouchDelayMs;
+ private boolean mAllAppsTouchTriggered;
// Only non-null when device supports having an All Apps button.
private @Nullable IconButtonView mTaskbarDivider;
@@ -126,12 +134,11 @@
mIsRtl = Utilities.isRtl(resources);
mTransientTaskbarMinWidth = resources.getDimension(R.dimen.transient_taskbar_min_width);
-
onDeviceProfileChanged(mActivityContext.getDeviceProfile());
int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
- if (enableTaskbarPinning()) {
+ if (enableTaskbarPinning() && !mActivityContext.isThreeButtonNav()) {
DeviceProfile deviceProfile = mActivityContext.getTransientTaskbarDeviceProfile();
actualIconSize = deviceProfile.taskbarIconSize;
}
@@ -153,27 +160,28 @@
// Needed to draw folder leave-behind when opening one.
setWillNotDraw(false);
- if (!mActivityContext.getPackageManager().hasSystemFeature(FEATURE_PC)) {
- mAllAppsButton = (IconButtonView) LayoutInflater.from(context)
- .inflate(R.layout.taskbar_all_apps_button, this, false);
- mAllAppsButton.setIconDrawable(resources.getDrawable(
- getAllAppsButton(isTransientTaskbar)));
- mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
- mAllAppsButton.setForegroundTint(
- mActivityContext.getColor(R.color.all_apps_button_color));
+ mAllAppsButton = (IconButtonView) LayoutInflater.from(context)
+ .inflate(R.layout.taskbar_all_apps_button, this, false);
+ mAllAppsButton.setIconDrawable(resources.getDrawable(
+ getAllAppsButton(isTransientTaskbar)));
+ mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
+ mAllAppsButton.setForegroundTint(
+ mActivityContext.getColor(R.color.all_apps_button_color));
- if (enableTaskbarPinning()) {
- mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
- R.layout.taskbar_divider,
- this, false);
- mTaskbarDivider.setIconDrawable(
- resources.getDrawable(R.drawable.taskbar_divider_button));
- mTaskbarDivider.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
- }
+ if (enableTaskbarPinning()) {
+ mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
+ R.layout.taskbar_divider,
+ this, false);
+ mTaskbarDivider.setIconDrawable(
+ resources.getDrawable(R.drawable.taskbar_divider_button));
+ mTaskbarDivider.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
}
// TODO: Disable touch events on QSB otherwise it can crash.
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+
+ // Default long press (touch) delay = 400ms
+ mAllAppsButtonTouchDelayMs = ViewConfiguration.getLongPressTimeout();
}
@DrawableRes
@@ -264,13 +272,22 @@
mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
if (mAllAppsButton != null) {
- mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener());
- mAllAppsButton.setOnLongClickListener(
- mControllerCallbacks.getAllAppsButtonLongClickListener());
+ mAllAppsButton.setOnClickListener(this::onAllAppsButtonClick);
+ mAllAppsButton.setOnLongClickListener(this::onAllAppsButtonLongClick);
+ mAllAppsButton.setOnTouchListener(this::onAllAppsButtonTouch);
mAllAppsButton.setHapticFeedbackEnabled(
mControllerCallbacks.isAllAppsButtonHapticFeedbackEnabled());
+ mAllAppsTouchRunnable = () -> {
+ mControllerCallbacks.triggerAllAppsButtonLongClick();
+ mAllAppsTouchTriggered = true;
+ };
+ AssistStateManager assistStateManager = AssistStateManager.INSTANCE.get(mContext);
+ if (DeviceConfigWrapper.get().getCustomLpaaThresholds()
+ && assistStateManager.getLPNHDurationMillis().isPresent()) {
+ mAllAppsButtonTouchDelayMs = assistStateManager.getLPNHDurationMillis().get();
+ }
}
- if (mTaskbarDivider != null) {
+ if (mTaskbarDivider != null && !mActivityContext.isThreeButtonNav()) {
mTaskbarDivider.setOnLongClickListener(
mControllerCallbacks.getTaskbarDividerLongClickListener());
mTaskbarDivider.setOnTouchListener(
@@ -282,7 +299,7 @@
removeView(view);
view.setOnClickListener(null);
view.setOnLongClickListener(null);
- if (!(view.getTag() instanceof FolderInfo)) {
+ if (!(view.getTag() instanceof CollectionInfo)) {
mActivityContext.getViewCache().recycleView(view.getSourceLayoutResId(), view);
}
view.setTag(null);
@@ -304,7 +321,6 @@
}
removeView(mQsb);
-
for (int i = 0; i < hotseatItemInfos.length; i++) {
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
if (hotseatItemInfo == null) {
@@ -316,8 +332,8 @@
boolean isCollection = false;
if (hotseatItemInfo.isPredictedItem()) {
expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
- } else if (hotseatItemInfo instanceof FolderInfo fi) {
- expectedLayoutResId = fi.itemType == ITEM_TYPE_APP_PAIR
+ } else if (hotseatItemInfo instanceof CollectionInfo ci) {
+ expectedLayoutResId = ci.itemType == ITEM_TYPE_APP_PAIR
? R.layout.app_pair_icon
: R.layout.folder_icon;
isCollection = true;
@@ -345,16 +361,18 @@
if (hotseatView == null) {
if (isCollection) {
- FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
+ CollectionInfo collectionInfo = (CollectionInfo) hotseatItemInfo;
switch (hotseatItemInfo.itemType) {
case ITEM_TYPE_FOLDER:
hotseatView = FolderIcon.inflateFolderAndIcon(
- expectedLayoutResId, mActivityContext, this, folderInfo);
+ expectedLayoutResId, mActivityContext, this,
+ (FolderInfo) collectionInfo);
((FolderIcon) hotseatView).setTextVisible(false);
break;
case ITEM_TYPE_APP_PAIR:
hotseatView = AppPairIcon.inflateIcon(
- expectedLayoutResId, mActivityContext, this, folderInfo);
+ expectedLayoutResId, mActivityContext, this,
+ (AppPairInfo) collectionInfo, DISPLAY_TASKBAR);
((AppPairIcon) hotseatView).setTextVisible(false);
break;
default:
@@ -467,6 +485,29 @@
iconEnd = centerAlignIconEnd + offset;
}
+ // Currently, we support only one device with display cutout and we only are concern about
+ // it when the bottom rect is present and non empty
+ DisplayCutout displayCutout = getDisplay().getCutout();
+ if (displayCutout != null && !displayCutout.getBoundingRectBottom().isEmpty()) {
+ Rect cutoutBottomRect = displayCutout.getBoundingRectBottom();
+ // when cutout present at the bottom of screen align taskbar icons to cutout offset
+ // if taskbar icon overlaps with cutout
+ int taskbarIconLeftBound = iconEnd - spaceNeeded;
+ int taskbarIconRightBound = iconEnd;
+
+ boolean doesTaskbarIconsOverlapWithCutout =
+ taskbarIconLeftBound <= cutoutBottomRect.centerX()
+ && cutoutBottomRect.centerX() <= taskbarIconRightBound;
+
+ if (doesTaskbarIconsOverlapWithCutout) {
+ if (!layoutRtl) {
+ iconEnd = spaceNeeded + cutoutBottomRect.width();
+ } else {
+ iconEnd = right - cutoutBottomRect.width();
+ }
+ }
+ }
+
sTmpRect.set(mIconLayoutBounds);
// Layout the children
@@ -663,4 +704,46 @@
}
return mAllAppsButton;
}
+
+ private boolean onAllAppsButtonTouch(View view, MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mAllAppsTouchTriggered = false;
+ MAIN_EXECUTOR.getHandler().postDelayed(
+ mAllAppsTouchRunnable, mAllAppsButtonTouchDelayMs);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ cancelAllAppsButtonTouch();
+ }
+ return false;
+ }
+
+ private void cancelAllAppsButtonTouch() {
+ MAIN_EXECUTOR.getHandler().removeCallbacks(mAllAppsTouchRunnable);
+ // ACTION_UP is first triggered, then click listener / long-click listener is triggered on
+ // the next frame, so we need to post twice and delay the reset.
+ if (mAllAppsButton != null) {
+ mAllAppsButton.post(() -> {
+ mAllAppsButton.post(() -> {
+ mAllAppsTouchTriggered = false;
+ });
+ });
+ }
+ }
+
+ private void onAllAppsButtonClick(View view) {
+ if (!mAllAppsTouchTriggered) {
+ mControllerCallbacks.triggerAllAppsButtonClick(view);
+ }
+ }
+
+ // Handle long click from Switch Access and Voice Access
+ private boolean onAllAppsButtonLongClick(View view) {
+ if (!MAIN_EXECUTOR.getHandler().hasCallbacks(mAllAppsTouchRunnable)
+ && !mAllAppsTouchTriggered) {
+ mControllerCallbacks.triggerAllAppsButtonLongClick();
+ }
+ return true;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index c841cac..3c646cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -46,20 +46,17 @@
return mActivity.getItemOnClickListener();
}
- public View.OnClickListener getAllAppsButtonClickListener() {
- return v -> {
- InteractionJankMonitorWrapper.begin(v, Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS,
- /* tag= */ "TASKBAR_BUTTON");
- mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
- mControllers.taskbarAllAppsController.toggle();
- };
+ /** Trigger All Apps button click action. */
+ protected void triggerAllAppsButtonClick(View v) {
+ InteractionJankMonitorWrapper.begin(v, Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS,
+ /* tag= */ "TASKBAR_BUTTON");
+ mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
+ mControllers.taskbarAllAppsController.toggle();
}
- public View.OnLongClickListener getAllAppsButtonLongClickListener() {
- return v -> {
- mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS);
- return true;
- };
+ /** Trigger All Apps button long click action. */
+ protected void triggerAllAppsButtonLongClick() {
+ mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS);
}
public boolean isAllAppsButtonHapticFeedbackEnabled() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 4b1963b..55745b5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -17,6 +17,7 @@
import static com.android.app.animation.Interpolators.FINAL_FRAME;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
@@ -71,6 +72,7 @@
import com.android.launcher3.views.IconButtonView;
import java.io.PrintWriter;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -78,7 +80,7 @@
*/
public class TaskbarViewController implements TaskbarControllers.LoggableTaskbarController {
- private static final String TAG = TaskbarViewController.class.getSimpleName();
+ private static final String TAG = "TaskbarViewController";
private static final Runnable NO_OP = () -> { };
@@ -337,11 +339,6 @@
private void updateTaskbarIconTranslationXForPinning() {
View[] iconViews = mTaskbarView.getIconViews();
float scale = mTaskbarIconTranslationXForPinning.value;
- float taskbarCenterX =
- mTaskbarView.getLeft() + (mTaskbarView.getRight() - mTaskbarView.getLeft()) / 2.0f;
-
- float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize);
-
float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
mTaskbarView.getAllAppsButtonTranslationXOffset(true));
float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
@@ -350,10 +347,26 @@
float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
persistentTaskbarAllAppsOffset);
+ // no x translation required when all apps button is the only icon in taskbar.
+ if (iconViews.length <= 1) {
+ allAppIconTranslateRange = 0f;
+ }
+
if (mIsRtl) {
allAppIconTranslateRange *= -1;
}
+ if (mActivity.isThreeButtonNav()) {
+ ((IconButtonView) mTaskbarView.getAllAppsButtonView())
+ .setTranslationXForTaskbarAllAppsIcon(allAppIconTranslateRange);
+ return;
+ }
+
+ float taskbarCenterX =
+ mTaskbarView.getLeft() + (mTaskbarView.getRight() - mTaskbarView.getLeft()) / 2.0f;
+
+ float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize);
+
float halfIconCount = iconViews.length / 2.0f;
for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
View iconView = iconViews[iconIndex];
@@ -369,7 +382,7 @@
-finalMarginScale * (iconIndex - halfIconCount));
}
- if (iconView.equals(mTaskbarView.getAllAppsButtonView()) && iconViews.length > 1) {
+ if (iconView.equals(mTaskbarView.getAllAppsButtonView())) {
((IconButtonView) iconView).setTranslationXForTaskbarAllAppsIcon(
allAppIconTranslateRange);
}
@@ -501,6 +514,31 @@
return mTaskbarView.getTaskbarDividerView();
}
+ /** Updates which icons are marked as running given the Set of currently running packages. */
+ public void updateIconViewsRunningStates(Set<String> runningPackages,
+ Set<String> minimizedPackages) {
+ for (View iconView : getIconViews()) {
+ if (iconView instanceof BubbleTextView btv) {
+ btv.updateRunningState(
+ getRunningAppState(btv.getTargetPackageName(), runningPackages,
+ minimizedPackages));
+ }
+ }
+ }
+
+ private BubbleTextView.RunningAppState getRunningAppState(
+ String packageName,
+ Set<String> runningPackages,
+ Set<String> minimizedPackages) {
+ if (minimizedPackages.contains(packageName)) {
+ return BubbleTextView.RunningAppState.MINIMIZED;
+ }
+ if (runningPackages.contains(packageName)) {
+ return BubbleTextView.RunningAppState.RUNNING;
+ }
+ return BubbleTextView.RunningAppState.NOT_RUNNING;
+ }
+
/**
* Defers any updates to the UI for the setup wizard animation.
*/
@@ -673,6 +711,12 @@
&& mIsStashed) {
// Prevent All Apps icon from appearing when going from hotseat to nav handle.
setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f));
+ } else if (enableScalingRevealHomeAnimation()) {
+ // Tighten clamp so that these icons do not linger as the spring settles.
+ setter.setViewAlpha(child, 0,
+ isToHome
+ ? Interpolators.clampToProgress(LINEAR, 0f, 0.07f)
+ : Interpolators.clampToProgress(LINEAR, 0.93f, 1f));
} else {
setter.setViewAlpha(child, 0,
isToHome
@@ -813,8 +857,8 @@
* 3) All Apps button
*/
public View getFirstIconMatch(Predicate<ItemInfo> matcher) {
- Predicate<ItemInfo> folderMatcher = ItemInfoMatcher.forFolderMatch(matcher);
- return mTaskbarView.getFirstMatch(matcher, folderMatcher);
+ Predicate<ItemInfo> collectionMatcher = ItemInfoMatcher.forFolderMatch(matcher);
+ return mTaskbarView.getFirstMatch(matcher, collectionMatcher);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 5424fcf..90ac872 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -17,6 +17,8 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.Flags.enablePredictiveBackGesture;
+import static com.android.launcher3.touch.AllAppsSwipeController.ALL_APPS_FADE_MANUAL;
+import static com.android.launcher3.touch.AllAppsSwipeController.SCRIM_FADE_MANUAL;
import android.animation.Animator;
import android.content.Context;
@@ -32,6 +34,7 @@
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
@@ -40,6 +43,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsViewController.TaskbarAllAppsCallbacks;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.views.AbstractSlideInView;
/** Wrapper for taskbar all apps with slide-in behavior. */
@@ -96,7 +100,7 @@
mAllAppsCallbacks.onAllAppsTransitionStart(true);
if (!animate) {
mAllAppsCallbacks.onAllAppsTransitionEnd(true);
- mTranslationShift = TRANSLATION_SHIFT_OPENED;
+ setTranslationShift(TRANSLATION_SHIFT_OPENED);
return;
}
@@ -113,8 +117,25 @@
@Override
protected void onOpenCloseAnimationPending(PendingAnimation animation) {
- mAllAppsCallbacks.onAllAppsAnimationPending(
- animation, mToTranslationShift == TRANSLATION_SHIFT_OPENED);
+ final boolean isOpening = mToTranslationShift == TRANSLATION_SHIFT_OPENED;
+
+ if (mActivityContext.getDeviceProfile().isPhone) {
+ final Interpolator allAppsFadeInterpolator =
+ isOpening ? ALL_APPS_FADE_MANUAL : Interpolators.reverse(ALL_APPS_FADE_MANUAL);
+ animation.setViewAlpha(mAppsView, 1 - mToTranslationShift, allAppsFadeInterpolator);
+ }
+
+ mAllAppsCallbacks.onAllAppsAnimationPending(animation, isOpening);
+ }
+
+ @Override
+ protected Interpolator getScrimInterpolator() {
+ if (mActivityContext.getDeviceProfile().isTablet) {
+ return super.getScrimInterpolator();
+ }
+ return mToTranslationShift == TRANSLATION_SHIFT_OPENED
+ ? SCRIM_FADE_MANUAL
+ : Interpolators.reverse(SCRIM_FADE_MANUAL);
}
/** The apps container inside this view. */
@@ -154,6 +175,9 @@
protected void onFinishInflate() {
super.onFinishInflate();
mAppsView = findViewById(R.id.apps_view);
+ if (mActivityContext.getDeviceProfile().isPhone) {
+ mAppsView.setAlpha(0);
+ }
mContent = mAppsView;
// Setup header protection for search bar, if enabled.
@@ -196,18 +220,17 @@
@Override
protected void dispatchDraw(Canvas canvas) {
- mAppsView.drawOnScrimWithScale(canvas, mSlideInViewScale.value);
+ // We should call drawOnScrimWithBottomOffset() rather than drawOnScrimWithScale(). Because
+ // for taskbar all apps, the scrim view is a child view of AbstractSlideInView. Thus scaling
+ // down in AbstractSlideInView#onScaleProgressChanged() with SCALE_PROPERTY has already
+ // done the job - there is no need to re-apply scale effect here. But it also means we need
+ // to pass extra bottom offset to background scrim to fill the bottom gap during predictive
+ // back swipe.
+ mAppsView.drawOnScrimWithBottomOffset(canvas, getBottomOffsetPx());
super.dispatchDraw(canvas);
}
@Override
- protected void onScaleProgressChanged() {
- super.onScaleProgressChanged();
- mAppsView.setClipChildren(!mIsBackProgressing);
- mAppsView.getAppsRecyclerViewContainer().setClipChildren(!mIsBackProgressing);
- }
-
- @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
setTranslationShift(mTranslationShift);
@@ -215,7 +238,9 @@
@Override
protected int getScrimColor(Context context) {
- return context.getColor(R.color.widgets_picker_scrim);
+ return mActivityContext.getDeviceProfile().isPhone
+ ? Themes.getAttrColor(context, R.attr.allAppsScrimColor)
+ : context.getColor(R.color.widgets_picker_scrim);
}
@Override
@@ -253,9 +278,29 @@
return getPopupContainer().isEventOverView(mAppsView.getVisibleContainerView(), ev);
}
+ /**
+ * In taskbar all apps search mode, we should scale down content inside all apps, rather
+ * than the whole all apps bottom sheet, to indicate we will navigate back within the all apps.
+ */
+ @Override
+ public boolean shouldAnimateContentViewInBackSwipe() {
+ return mAllAppsCallbacks.canHandleSearchBackInvoked();
+ }
+
+ @Override
+ protected void onUserSwipeToDismissProgressChanged() {
+ super.onUserSwipeToDismissProgressChanged();
+ mAppsView.setClipChildren(!mIsDismissInProgress);
+ mAppsView.getAppsRecyclerViewContainer().setClipChildren(!mIsDismissInProgress);
+ }
+
@Override
public void onBackInvoked() {
- if (!mAllAppsCallbacks.handleSearchBackInvoked()) {
+ if (mAllAppsCallbacks.handleSearchBackInvoked()) {
+ // We need to scale back taskbar all apps if we navigate back within search inside all
+ // apps
+ post(this::animateSwipeToDismissProgressToStart);
+ } else {
super.onBackInvoked();
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index ba4fa45..52f7176 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -143,6 +143,11 @@
}
}
+ /** Check if search session can handle back. This check doesn't perform any action. */
+ boolean canHandleSearchBackInvoked() {
+ return mSearchSessionController.canHandleBackInvoked();
+ }
+
/** Invoked on back press, returning {@code true} if the search session handled it. */
boolean handleSearchBackInvoked() {
return mSearchSessionController.handleBackInvoked();
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
index 3d15fbd..4d0b376 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
@@ -49,6 +49,8 @@
/** Creates a [PreDragCondition] for [view], if it is a search result that requires one. */
open fun createPreDragConditionForSearch(view: View): PreDragCondition? = null
+ open fun canHandleBackInvoked(): Boolean = false
+
open fun handleBackInvoked(): Boolean = false
open fun onAllAppsAnimationPending(
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index aa2b29d..25939e1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -15,38 +15,43 @@
*/
package com.android.launcher3.taskbar.bubbles
+import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
+import android.graphics.Matrix
import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
-import android.graphics.drawable.ShapeDrawable
import com.android.app.animation.Interpolators
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapToRange
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
-import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.wm.shell.common.TriangleShape
+import com.android.launcher3.popup.RoundedArrowDrawable
+import kotlin.math.max
+import kotlin.math.min
/** Drawable for the background of the bubble bar. */
-class BubbleBarBackground(context: TaskbarActivityContext, private val backgroundHeight: Float) :
- Drawable() {
+class BubbleBarBackground(context: Context, private var backgroundHeight: Float) : Drawable() {
- private val DARK_THEME_SHADOW_ALPHA = 51f
- private val LIGHT_THEME_SHADOW_ALPHA = 25f
-
- private val paint: Paint = Paint()
- private val pointerSize: Float
+ private val fillPaint: Paint = Paint()
+ private val strokePaint: Paint = Paint()
+ private val arrowWidth: Float
+ private val arrowHeight: Float
+ private val arrowTipRadius: Float
+ private val arrowVisibleHeight: Float
private val shadowAlpha: Float
private var shadowBlur = 0f
private var keyShadowDistance = 0f
+ private var arrowHeightFraction = 1f
var arrowPositionX: Float = 0f
private set
+
private var showingArrow: Boolean = false
- private var arrowDrawable: ShapeDrawable
var width: Float = 0f
@@ -67,28 +72,31 @@
}
init {
- paint.color = context.getColor(R.color.taskbar_background)
- paint.flags = Paint.ANTI_ALIAS_FLAG
- paint.style = Paint.Style.FILL
-
val res = context.resources
+ // configure fill paint
+ fillPaint.color = context.getColor(R.color.taskbar_background)
+ fillPaint.flags = Paint.ANTI_ALIAS_FLAG
+ fillPaint.style = Paint.Style.FILL
+ // configure stroke paint
+ strokePaint.color = context.getColor(R.color.taskbar_stroke)
+ strokePaint.flags = Paint.ANTI_ALIAS_FLAG
+ strokePaint.style = Paint.Style.STROKE
+ strokePaint.strokeWidth = res.getDimension(R.dimen.transient_taskbar_stroke_width)
+ // apply theme alpha attributes
+ if (Utilities.isDarkTheme(context)) {
+ strokePaint.alpha = DARK_THEME_STROKE_ALPHA
+ shadowAlpha = DARK_THEME_SHADOW_ALPHA
+ } else {
+ strokePaint.alpha = LIGHT_THEME_STROKE_ALPHA
+ shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
+ }
+
shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
- pointerSize = res.getDimension(R.dimen.bubblebar_pointer_size)
-
- shadowAlpha =
- if (Utilities.isDarkTheme(context)) {
- DARK_THEME_SHADOW_ALPHA
- } else {
- LIGHT_THEME_SHADOW_ALPHA
- }
-
- arrowDrawable =
- ShapeDrawable(TriangleShape.create(pointerSize, pointerSize, /* pointUp= */ true))
- arrowDrawable.setBounds(0, 0, pointerSize.toInt(), pointerSize.toInt())
- arrowDrawable.paint.flags = Paint.ANTI_ALIAS_FLAG
- arrowDrawable.paint.style = Paint.Style.FILL
- arrowDrawable.paint.color = context.getColor(R.color.taskbar_background)
+ arrowWidth = res.getDimension(R.dimen.bubblebar_pointer_width)
+ arrowHeight = res.getDimension(R.dimen.bubblebar_pointer_height)
+ arrowVisibleHeight = res.getDimension(R.dimen.bubblebar_pointer_visible_size)
+ arrowTipRadius = res.getDimension(R.dimen.bubblebar_pointer_radius)
}
fun showArrow(show: Boolean) {
@@ -106,49 +114,95 @@
// TODO (b/277359345): Should animate the alpha similar to taskbar (see TaskbarDragLayer)
// Draw shadows.
val newShadowAlpha =
- mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
- paint.setShadowLayer(
+ mapToRange(fillPaint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
+ fillPaint.setShadowLayer(
shadowBlur,
0f,
keyShadowDistance,
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
)
- arrowDrawable.paint.setShadowLayer(
- shadowBlur,
- 0f,
- keyShadowDistance,
- setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
- )
+ // Create background path
+ val backgroundPath = Path()
+ val topOffset = backgroundHeight - bounds.height().toFloat()
+ val radius = backgroundHeight / 2f
+ val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - width)
+ val right = bounds.left + (if (anchorLeft) width else bounds.width().toFloat())
+ val top = bounds.top - topOffset + arrowVisibleHeight
+
+ val bottom = bounds.top + bounds.height().toFloat()
+ backgroundPath.addRoundRect(left, top, right, bottom, radius, radius, Path.Direction.CW)
+ addArrowPathIfNeeded(backgroundPath, topOffset)
// Draw background.
- val radius = backgroundHeight / 2f
- val left = if (anchorLeft) 0f else canvas.width.toFloat() - width
- val right = if (anchorLeft) width else canvas.width.toFloat()
- canvas.drawRoundRect(left, 0f, right, canvas.height.toFloat(), radius, radius, paint)
-
- if (showingArrow) {
- // Draw arrow.
- val transX = arrowPositionX - pointerSize / 2f
- canvas.translate(transX, -pointerSize)
- arrowDrawable.draw(canvas)
- }
-
+ canvas.drawPath(backgroundPath, fillPaint)
+ canvas.drawPath(backgroundPath, strokePaint)
canvas.restore()
}
+ private fun addArrowPathIfNeeded(sourcePath: Path, topOffset: Float) {
+ if (!showingArrow || arrowHeightFraction <= 0) return
+ val arrowPath = Path()
+ RoundedArrowDrawable.addDownPointingRoundedTriangleToPath(
+ arrowWidth,
+ arrowHeight,
+ arrowTipRadius,
+ arrowPath
+ )
+ // flip it horizontally
+ val pathTransform = Matrix()
+ pathTransform.setRotate(180f, arrowWidth * 0.5f, arrowHeight * 0.5f)
+ arrowPath.transform(pathTransform)
+ // shift to arrow position
+ val arrowStart = bounds.left + arrowPositionX - (arrowWidth / 2f)
+ val arrowTop = (1 - arrowHeightFraction) * arrowVisibleHeight - topOffset
+ arrowPath.offset(arrowStart, arrowTop)
+ // union with rectangle
+ sourcePath.op(arrowPath, Path.Op.UNION)
+ }
+
override fun getOpacity(): Int {
- return paint.alpha
+ return when (fillPaint.alpha) {
+ 255 -> PixelFormat.OPAQUE
+ 0 -> PixelFormat.TRANSPARENT
+ else -> PixelFormat.TRANSLUCENT
+ }
}
override fun setAlpha(alpha: Int) {
- paint.alpha = alpha
+ fillPaint.alpha = alpha
+ invalidateSelf()
+ }
+
+ override fun getAlpha(): Int {
+ return fillPaint.alpha
}
override fun setColorFilter(colorFilter: ColorFilter?) {
- paint.colorFilter = colorFilter
+ fillPaint.colorFilter = colorFilter
}
- fun setArrowAlpha(alpha: Int) {
- arrowDrawable.paint.alpha = alpha
+ fun setBackgroundHeight(newHeight: Float) {
+ backgroundHeight = newHeight
+ }
+
+ /**
+ * Set fraction of the arrow height that should be displayed. Allowed values range are [0..1].
+ * If value passed is out of range it will be converted to the closest value in tha allowed
+ * range.
+ */
+ fun setArrowHeightFraction(arrowHeightFraction: Float) {
+ var newHeightFraction = arrowHeightFraction
+ if (newHeightFraction !in 0f..1f) {
+ newHeightFraction = min(max(newHeightFraction, 0f), 1f)
+ }
+ this.arrowHeightFraction = newHeightFraction
+ invalidateSelf()
+ }
+
+ companion object {
+ private const val DARK_THEME_STROKE_ALPHA = 51
+ private const val LIGHT_THEME_STROKE_ALPHA = 41
+ private const val DARK_THEME_SHADOW_ALPHA = 51f
+ private const val LIGHT_THEME_SHADOW_ALPHA = 25f
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 6dc7db7..028df34 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -31,8 +31,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-import static java.lang.Math.abs;
-
import android.annotation.BinderThread;
import android.annotation.Nullable;
import android.app.Notification;
@@ -47,7 +45,6 @@
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Point;
-import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -67,12 +64,12 @@
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.taskbar.TaskbarControllers;
-import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.wm.shell.Flags;
import com.android.wm.shell.bubbles.IBubblesListener;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.common.bubbles.BubbleInfo;
import com.android.wm.shell.common.bubbles.RemovedBubble;
@@ -93,7 +90,7 @@
*/
public class BubbleBarController extends IBubblesListener.Stub {
- private static final String TAG = BubbleBarController.class.getSimpleName();
+ private static final String TAG = "BubbleBarController";
private static final boolean DEBUG = false;
/**
@@ -116,7 +113,7 @@
|| SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
}
- private static final int MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
+ 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
@@ -124,11 +121,11 @@
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED
| SYSUI_STATE_IME_SWITCHER_SHOWING;
- private static final int MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
+ private static final long MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
- private static final int MASK_SYSUI_LOCKED = SYSUI_STATE_BOUNCER_SHOWING
+ private static final long MASK_SYSUI_LOCKED = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
@@ -138,7 +135,6 @@
private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor(
new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND));
- private final Executor mMainExecutor;
private final LauncherApps mLauncherApps;
private final BubbleIconFactory mIconFactory;
private final SystemUiProxy mSystemUiProxy;
@@ -146,23 +142,31 @@
private BubbleBarItem mSelectedBubble;
private BubbleBarOverflow mOverflowBubble;
+ private ImeVisibilityChecker mImeVisibilityChecker;
private BubbleBarViewController mBubbleBarViewController;
private BubbleStashController mBubbleStashController;
private BubbleStashedHandleViewController mBubbleStashedHandleViewController;
+ private BubblePinController mBubblePinController;
+
+ // Cache last sent top coordinate to avoid sending duplicate updates to shell
+ private int mLastSentBubbleBarTop;
/**
* Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses
* {@link BubbleBarBubble}s so that it can be used to update the views.
*/
private static class BubbleBarViewUpdate {
+ final boolean initialState;
boolean expandedChanged;
boolean expanded;
boolean shouldShowEducation;
String selectedBubbleKey;
String suppressedBubbleKey;
String unsuppressedBubbleKey;
+ BubbleBarLocation bubbleBarLocation;
List<RemovedBubble> removedBubbles;
List<String> bubbleKeysInOrder;
+ Point expandedViewDropTargetSize;
// These need to be loaded in the background
BubbleBarBubble addedBubble;
@@ -170,14 +174,17 @@
List<BubbleBarBubble> currentBubbles;
BubbleBarViewUpdate(BubbleBarUpdate update) {
+ initialState = update.initialState;
expandedChanged = update.expandedChanged;
expanded = update.expanded;
shouldShowEducation = update.shouldShowEducation;
selectedBubbleKey = update.selectedBubbleKey;
suppressedBubbleKey = update.suppressedBubbleKey;
unsuppressedBubbleKey = update.unsupressedBubbleKey;
+ bubbleBarLocation = update.bubbleBarLocation;
removedBubbles = update.removedBubbles;
bubbleKeysInOrder = update.bubbleKeysInOrder;
+ expandedViewDropTargetSize = update.expandedViewDropTargetSize;
}
}
@@ -190,7 +197,6 @@
if (sBubbleBarEnabled) {
mSystemUiProxy.setBubblesListener(this);
}
- mMainExecutor = MAIN_EXECUTOR;
mLauncherApps = context.getSystemService(LauncherApps.class);
mIconFactory = new BubbleIconFactory(context,
context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size),
@@ -204,10 +210,14 @@
mSystemUiProxy.setBubblesListener(null);
}
- public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
+ /** Initializes controllers. */
+ public void init(BubbleControllers bubbleControllers,
+ ImeVisibilityChecker imeVisibilityChecker) {
+ mImeVisibilityChecker = imeVisibilityChecker;
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
+ mBubblePinController = bubbleControllers.bubblePinController;
bubbleControllers.runAfterInit(() -> {
mBubbleBarViewController.setHiddenForBubbles(
@@ -215,7 +225,8 @@
mBubbleStashedHandleViewController.setHiddenForBubbles(
!sBubbleBarEnabled || mBubbles.isEmpty());
mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
- key -> setSelectedBubble(mBubbles.get(key)));
+ key -> setSelectedBubbleInternal(mBubbles.get(key)));
+ mBubbleBarViewController.setBoundsChangeListener(this::onBubbleBarBoundsChanged);
});
}
@@ -228,11 +239,12 @@
private void createAndAddOverflowIfNeeded() {
if (mOverflowBubble == null) {
BubbleBarOverflow overflow = createOverflow(mContext);
- mMainExecutor.execute(() -> {
+ MAIN_EXECUTOR.execute(() -> {
// we're on the main executor now, so check that the overflow hasn't been created
// again to avoid races.
if (mOverflowBubble == null) {
- mBubbleBarViewController.addBubble(overflow);
+ mBubbleBarViewController.addBubble(
+ overflow, /* isExpanding= */ false, /* suppressAnimation= */ true);
mOverflowBubble = overflow;
}
});
@@ -242,7 +254,7 @@
/**
* Updates the bubble bar, handle bar, and stash controllers based on sysui state flags.
*/
- public void updateStateForSysuiFlags(int flags) {
+ public void updateStateForSysuiFlags(@SystemUiStateFlags long flags) {
boolean hideBubbleBar = (flags & MASK_HIDE_BUBBLE_BAR) != 0;
mBubbleBarViewController.setHiddenForSysui(hideBubbleBar);
@@ -289,19 +301,26 @@
}
viewUpdate.currentBubbles = currentBubbles;
}
- mMainExecutor.execute(() -> applyViewChanges(viewUpdate));
+ MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate));
});
} else {
// No bubbles to load, immediately apply the changes.
BUBBLE_STATE_EXECUTOR.execute(
- () -> mMainExecutor.execute(() -> applyViewChanges(viewUpdate)));
+ () -> MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate)));
}
}
private void applyViewChanges(BubbleBarViewUpdate update) {
final boolean isCollapsed = (update.expandedChanged && !update.expanded)
|| (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
- BubbleBarItem previouslySelectedBubble = mSelectedBubble;
+ final boolean isExpanding = update.expandedChanged && update.expanded;
+ // don't animate bubbles if this is the initial state because we may be unfolding or
+ // enabling gesture nav. also suppress animation if the bubble bar is hidden for sysui e.g.
+ // the shade is open, or we're locked.
+ final boolean suppressAnimation =
+ update.initialState || mBubbleBarViewController.isHiddenForSysui()
+ || mImeVisibilityChecker.isImeVisible();
+
BubbleBarBubble bubbleToSelect = null;
if (!update.removedBubbles.isEmpty()) {
for (int i = 0; i < update.removedBubbles.size(); i++) {
@@ -317,7 +336,7 @@
}
if (update.addedBubble != null) {
mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
- mBubbleBarViewController.addBubble(update.addedBubble);
+ mBubbleBarViewController.addBubble(update.addedBubble, isExpanding, suppressAnimation);
if (isCollapsed) {
// If we're collapsed, the most recently added bubble will be selected.
bubbleToSelect = update.addedBubble;
@@ -330,7 +349,7 @@
BubbleBarBubble bubble = update.currentBubbles.get(i);
if (bubble != null) {
mBubbles.put(bubble.getKey(), bubble);
- mBubbleBarViewController.addBubble(bubble);
+ mBubbleBarViewController.addBubble(bubble, isExpanding, suppressAnimation);
if (isCollapsed) {
// If we're collapsed, the most recently added bubble will be selected.
bubbleToSelect = bubble;
@@ -357,6 +376,7 @@
BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
// If we're not stashed, we're visible so animate
bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
+ mBubbleBarViewController.animateBubbleNotification(bb, /* isExpanding= */ false);
}
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
// Create the new list
@@ -385,10 +405,7 @@
}
}
if (bubbleToSelect != null) {
- setSelectedBubble(bubbleToSelect);
- if (previouslySelectedBubble == null) {
- mBubbleStashController.animateToInitialState(update.expanded);
- }
+ setSelectedBubbleInternal(bubbleToSelect);
}
if (update.shouldShowEducation) {
mBubbleBarViewController.prepareToShowEducation();
@@ -400,6 +417,14 @@
Log.w(TAG, "expansion was changed but is the same");
}
}
+ if (update.bubbleBarLocation != null) {
+ if (update.bubbleBarLocation != mBubbleBarViewController.getBubbleBarLocation()) {
+ updateBubbleBarLocationInternal(update.bubbleBarLocation);
+ }
+ }
+ if (update.expandedViewDropTargetSize != null) {
+ mBubblePinController.setDropTargetSize(update.expandedViewDropTargetSize);
+ }
}
/** Tells WMShell to show the currently selected bubble. */
@@ -414,7 +439,8 @@
info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
mSelectedBubble.getView().updateDotVisibility(true /* animate */);
}
- mSystemUiProxy.showBubble(getSelectedBubbleKey(), getExpandedBubbleBarDisplayBounds());
+ mLastSentBubbleBarTop = mBarView.getRestingTopPositionOnScreen();
+ mSystemUiProxy.showBubble(getSelectedBubbleKey(), mLastSentBubbleBarTop);
} else {
Log.w(TAG, "Trying to show the selected bubble but it's null");
}
@@ -423,7 +449,7 @@
/** Updates the currently selected bubble for launcher views and tells WMShell to show it. */
public void showAndSelectBubble(BubbleBarItem b) {
if (DEBUG) Log.w(TAG, "showingSelectedBubble: " + b.getKey());
- setSelectedBubble(b);
+ setSelectedBubbleInternal(b);
showSelectedBubble();
}
@@ -432,7 +458,7 @@
* WMShell that the selection has changed, that should go through either
* {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}.
*/
- private void setSelectedBubble(BubbleBarItem b) {
+ private void setSelectedBubbleInternal(BubbleBarItem b) {
if (!Objects.equals(b, mSelectedBubble)) {
if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
mSelectedBubble = b;
@@ -451,6 +477,27 @@
return null;
}
+ /**
+ * Set a new bubble bar location.
+ * <p>
+ * Updates the value locally in Launcher and in WMShell.
+ */
+ public void updateBubbleBarLocation(BubbleBarLocation location) {
+ updateBubbleBarLocationInternal(location);
+ mSystemUiProxy.setBubbleBarLocation(location);
+ }
+
+ private void updateBubbleBarLocationInternal(BubbleBarLocation location) {
+ mBubbleBarViewController.setBubbleBarLocation(location);
+ mBubbleStashController.setBubbleBarLocation(location);
+ }
+
+ @Override
+ public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ MAIN_EXECUTOR.execute(
+ () -> mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation));
+ }
+
//
// Loading data for the bubbles
//
@@ -582,27 +629,17 @@
return mIconFactory.createBadgedIconBitmap(drawable).icon;
}
- /**
- * Get bounds of the bubble bar as if it would be expanded.
- * Calculates the bounds instead of retrieving current view location as the view may be
- * animating.
- */
- private Rect getExpandedBubbleBarDisplayBounds() {
- Point displaySize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
- Rect currentBarBounds = mBarView.getBubbleBarBounds();
- Rect location = new Rect();
- // currentBarBounds is only useful for distance from left or right edge.
- // It contains the current bounds, calculate the expanded bounds.
- if (mBarView.isOnLeft()) {
- location.left = currentBarBounds.left;
- location.right = (int) (currentBarBounds.left + mBarView.expandedWidth());
- } else {
- location.left = (int) (currentBarBounds.right - mBarView.expandedWidth());
- location.right = currentBarBounds.right;
+ private void onBubbleBarBoundsChanged() {
+ int newTop = mBarView.getRestingTopPositionOnScreen();
+ if (newTop != mLastSentBubbleBarTop) {
+ mLastSentBubbleBarTop = newTop;
+ mSystemUiProxy.updateBubbleBarTopOnScreen(newTop);
}
- final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY());
- location.top = displaySize.y - mBarView.getHeight() - translation;
- location.bottom = displaySize.y - translation;
- return location;
+ }
+
+ /** Interface for checking whether the IME is visible. */
+ public interface ImeVisibilityChecker {
+ /** Whether the IME is visible. */
+ boolean isImeVisible();
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarPinController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarPinController.kt
new file mode 100644
index 0000000..9e5ffc9
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarPinController.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Point
+import android.view.Gravity.BOTTOM
+import android.view.Gravity.LEFT
+import android.view.Gravity.RIGHT
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.R
+import com.android.wm.shell.common.bubbles.BaseBubblePinController
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
+
+/**
+ * Controller to manage pinning bubble bar to left or right when dragging starts from the bubble bar
+ */
+class BubbleBarPinController(
+ private val context: Context,
+ private val container: FrameLayout,
+ screenSizeProvider: () -> Point
+) : BaseBubblePinController(screenSizeProvider) {
+
+ private lateinit var bubbleBarViewController: BubbleBarViewController
+ private lateinit var bubbleStashController: BubbleStashController
+ private var exclRectWidth: Float = 0f
+ private var exclRectHeight: Float = 0f
+
+ private var dropTargetView: View? = null
+
+ fun init(bubbleControllers: BubbleControllers) {
+ bubbleBarViewController = bubbleControllers.bubbleBarViewController
+ bubbleStashController = bubbleControllers.bubbleStashController
+ exclRectWidth = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width)
+ exclRectHeight = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_height)
+ }
+
+ override fun getExclusionRectWidth(): Float {
+ return exclRectWidth
+ }
+
+ override fun getExclusionRectHeight(): Float {
+ return exclRectHeight
+ }
+
+ override fun getDropTargetView(): View? {
+ return dropTargetView
+ }
+
+ override fun removeDropTargetView(view: View) {
+ container.removeView(view)
+ dropTargetView = null
+ }
+
+ override fun createDropTargetView(): View {
+ return LayoutInflater.from(context)
+ .inflate(R.layout.bubble_bar_drop_target, container, false)
+ .also { view ->
+ dropTargetView = view
+ container.addView(view)
+ }
+ }
+
+ @SuppressLint("RtlHardcoded")
+ override fun updateLocation(location: BubbleBarLocation) {
+ val onLeft = location.isOnLeft(container.isLayoutRtl)
+
+ val bounds = bubbleBarViewController.bubbleBarBounds
+ val horizontalMargin = bubbleBarViewController.horizontalMargin
+ dropTargetView?.updateLayoutParams<FrameLayout.LayoutParams> {
+ width = bounds.width()
+ height = bounds.height()
+ gravity = BOTTOM or (if (onLeft) LEFT else RIGHT)
+ leftMargin = horizontalMargin
+ rightMargin = horizontalMargin
+ bottomMargin = -bubbleStashController.bubbleBarTranslationY.toInt()
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 8f693a6..c7c63e8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -15,21 +15,38 @@
*/
package com.android.launcher3.taskbar.bubbles;
+import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.LayoutDirection;
import android.util.Log;
+import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import androidx.dynamicanimation.animation.SpringForce;
+
import com.android.launcher3.R;
-import com.android.launcher3.taskbar.TaskbarActivityContext;
-import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.util.DisplayController;
+import com.android.wm.shell.Flags;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import java.util.List;
import java.util.function.Consumer;
@@ -62,16 +79,51 @@
*/
public class BubbleBarView extends FrameLayout {
- private static final String TAG = BubbleBarView.class.getSimpleName();
+ private static final String TAG = "BubbleBarView";
// TODO: (b/273594744) calculate the amount of space we have and base the max on that
// if it's smaller than 5.
private static final int MAX_BUBBLES = 5;
+ private static final int MAX_VISIBLE_BUBBLES_COLLAPSED = 2;
private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
private static final int WIDTH_ANIMATION_DURATION_MS = 200;
+ private static final int SCALE_ANIMATION_DURATION_MS = 200;
+
+ private static final long FADE_OUT_ANIM_ALPHA_DURATION_MS = 50L;
+ private static final long FADE_OUT_ANIM_ALPHA_DELAY_MS = 50L;
+ private static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L;
+ // During fade out animation we shift the bubble bar 1/80th of the screen width
+ private static final float FADE_OUT_ANIM_POSITION_SHIFT = 1 / 80f;
+
+ private static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L;
+ // Use STIFFNESS_MEDIUMLOW which is not defined in the API constants
+ private static final float FADE_IN_ANIM_POSITION_SPRING_STIFFNESS = 400f;
+ // During fade in animation we shift the bubble bar 1/60th of the screen width
+ private static final float FADE_IN_ANIM_POSITION_SHIFT = 1 / 60f;
+
+ private static final int SCALE_IN_ANIMATION_DURATION_MS = 250;
+
+ /**
+ * Custom property to set alpha value for the bar view while a bubble is being dragged.
+ * Skips applying alpha to the dragged bubble.
+ */
+ private static final FloatProperty<BubbleBarView> BUBBLE_DRAG_ALPHA =
+ new FloatProperty<>("bubbleDragAlpha") {
+ @Override
+ public void setValue(BubbleBarView bubbleBarView, float alpha) {
+ bubbleBarView.setAlphaDuringBubbleDrag(alpha);
+ }
+
+ @Override
+ public Float get(BubbleBarView bubbleBarView) {
+ return bubbleBarView.mAlphaDuringDrag;
+ }
+ };
private final BubbleBarBackground mBubbleBarBackground;
+ private boolean mIsAnimatingNewBubble = false;
+
/**
* The current bounds of all the bubble bar. Note that these bounds may not account for
* translation. The bounds should be retrieved using {@link #getBubbleBarBounds()} which
@@ -80,17 +132,24 @@
private final Rect mBubbleBarBounds = new Rect();
// The amount the bubbles overlap when they are stacked in the bubble bar
private final float mIconOverlapAmount;
- // The spacing between the bubbles when they are expanded in the bubble bar
- private final float mIconSpacing;
+ // The spacing between the bubbles when bubble bar is expanded
+ private final float mExpandedBarIconsSpacing;
+ // The spacing between the bubbles and the borders of the bubble bar
+ private float mBubbleBarPadding;
// The size of a bubble in the bar
- private final float mIconSize;
+ private float mIconSize;
+ // The scale of bubble icons
+ private float mIconScale = 1f;
// The elevation of the bubbles within the bar
private final float mBubbleElevation;
-
+ private final float mDragElevation;
+ private final int mPointerSize;
// Whether the bar is expanded (i.e. the bubble activity is being displayed).
private boolean mIsBarExpanded = false;
// The currently selected bubble view.
+ @Nullable
private BubbleView mSelectedBubbleView;
+ private BubbleBarLocation mBubbleBarLocation = BubbleBarLocation.DEFAULT;
// The click listener when the bubble bar is collapsed.
private View.OnClickListener mOnClickListener;
@@ -102,6 +161,14 @@
// collapsed state and 1 to the fully expanded state.
private final ValueAnimator mWidthAnimator = ValueAnimator.ofFloat(0, 1);
+ /** An animator used for scaling in a new bubble to the bubble bar while expanded. */
+ @Nullable
+ private ValueAnimator mNewBubbleScaleInAnimator = null;
+ @Nullable
+ private ValueAnimator mScalePaddingAnimator;
+ @Nullable
+ private Animator mBubbleBarLocationAnimator = null;
+
// We don't reorder the bubbles when they are expanded as it could be jarring for the user
// this runnable will be populated with any reordering of the bubbles that should be applied
// once they are collapsed.
@@ -111,8 +178,15 @@
@Nullable
private Consumer<String> mUpdateSelectedBubbleAfterCollapse;
+ private boolean mDragging;
+
@Nullable
private BubbleView mDraggedBubbleView;
+ private float mAlphaDuringDrag = 1f;
+
+ private Controller mController;
+
+ private int mPreviousLayoutDirection = LayoutDirection.UNDEFINED;
public BubbleBarView(Context context) {
this(context, null);
@@ -128,63 +202,142 @@
public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- TaskbarActivityContext activityContext = ActivityContext.lookupContext(context);
-
setAlpha(0);
setVisibility(INVISIBLE);
mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
- mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
+ mBubbleBarPadding = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
mIconSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+ mExpandedBarIconsSpacing = getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_expanded_icon_spacing);
mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
+ mDragElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_drag_elevation);
+ mPointerSize = getResources()
+ .getDimensionPixelSize(R.dimen.bubblebar_pointer_visible_size);
+
setClipToPadding(false);
- mBubbleBarBackground = new BubbleBarBackground(activityContext,
- getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
+ mBubbleBarBackground = new BubbleBarBackground(context, getBubbleBarExpandedHeight());
setBackgroundDrawable(mBubbleBarBackground);
mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS);
- mWidthAnimator.addUpdateListener(animation -> {
- updateChildrenRenderNodeProperties();
- invalidate();
- });
- mWidthAnimator.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationCancel(Animator animation) {
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- mBubbleBarBackground.showArrow(mIsBarExpanded);
- if (!mIsBarExpanded && mReorderRunnable != null) {
- mReorderRunnable.run();
- mReorderRunnable = null;
- }
- // If the bar was just collapsed and the overflow was the last bubble that was
- // selected, set the first bubble as selected.
- if (!mIsBarExpanded && mUpdateSelectedBubbleAfterCollapse != null
- && mSelectedBubbleView.getBubble() instanceof BubbleBarOverflow) {
- BubbleView firstBubble = (BubbleView) getChildAt(0);
- mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
- }
- updateWidth();
- }
+ addAnimationCallBacks(mWidthAnimator,
+ /* onStart= */ () -> mBubbleBarBackground.showArrow(true),
+ /* onEnd= */ () -> {
+ mBubbleBarBackground.showArrow(mIsBarExpanded);
+ if (!mIsBarExpanded && mReorderRunnable != null) {
+ mReorderRunnable.run();
+ mReorderRunnable = null;
+ }
+ // If the bar was just collapsed and the overflow was the last bubble that was
+ // selected, set the first bubble as selected.
+ if (!mIsBarExpanded && mUpdateSelectedBubbleAfterCollapse != null
+ && mSelectedBubbleView != null
+ && mSelectedBubbleView.getBubble() instanceof BubbleBarOverflow) {
+ BubbleView firstBubble = (BubbleView) getChildAt(0);
+ mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
+ }
+ updateWidth();
+ },
+ /* onUpdate= */ animator -> {
+ updateBubblesLayoutProperties(mBubbleBarLocation);
+ invalidate();
+ });
+ }
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- @Override
- public void onAnimationStart(Animator animation) {
- mBubbleBarBackground.showArrow(true);
- }
- });
+ /**
+ * Animates icon sizes and spacing between icons and bubble bar borders.
+ *
+ * @param newIconSize new icon size
+ * @param newBubbleBarPadding spacing between icons and bubble bar borders.
+ */
+ public void animateBubbleBarIconSize(float newIconSize, float newBubbleBarPadding) {
+ if (!isIconSizeOrPaddingUpdated(newIconSize, newBubbleBarPadding)) {
+ return;
+ }
+ if (!Flags.animateBubbleSizeChange()) {
+ setIconSizeAndPadding(newIconSize, newBubbleBarPadding);
+ }
+ if (mScalePaddingAnimator != null && mScalePaddingAnimator.isRunning()) {
+ mScalePaddingAnimator.cancel();
+ }
+ ValueAnimator scalePaddingAnimator = ValueAnimator.ofFloat(0f, 1f);
+ scalePaddingAnimator.setDuration(SCALE_ANIMATION_DURATION_MS);
+ boolean isPaddingUpdated = isPaddingUpdated(newBubbleBarPadding);
+ boolean isIconSizeUpdated = isIconSizeUpdated(newIconSize);
+ float initialScale = mIconScale;
+ float initialPadding = mBubbleBarPadding;
+ float targetScale = newIconSize / getScaledIconSize();
+
+ addAnimationCallBacks(scalePaddingAnimator,
+ /* onStart= */ null,
+ /* onEnd= */ () -> setIconSizeAndPadding(newIconSize, newBubbleBarPadding),
+ /* onUpdate= */ animator -> {
+ float transitionProgress = (float) animator.getAnimatedValue();
+ if (isIconSizeUpdated) {
+ mIconScale =
+ initialScale + (targetScale - initialScale) * transitionProgress;
+ }
+ if (isPaddingUpdated) {
+ mBubbleBarPadding = initialPadding
+ + (newBubbleBarPadding - initialPadding) * transitionProgress;
+ }
+ updateBubblesLayoutProperties(mBubbleBarLocation);
+ invalidate();
+ });
+ scalePaddingAnimator.start();
+ mScalePaddingAnimator = scalePaddingAnimator;
+ }
+
+ @Override
+ public void setTranslationX(float translationX) {
+ super.setTranslationX(translationX);
+ if (mDraggedBubbleView != null) {
+ // Apply reverse of the translation as an offset to the dragged view. This ensures
+ // that the dragged bubble stays at the current location on the screen and its
+ // position is not affected by the parent translation.
+ mDraggedBubbleView.setOffsetX(-translationX);
+ }
+ }
+
+ /**
+ * Sets new icon sizes and newBubbleBarPadding between icons and bubble bar borders.
+ *
+ * @param newIconSize new icon size
+ * @param newBubbleBarPadding newBubbleBarPadding between icons and bubble bar borders.
+ */
+ public void setIconSizeAndPadding(float newIconSize, float newBubbleBarPadding) {
+ // TODO(b/335457839): handle new bubble animation during the size change
+ if (!isIconSizeOrPaddingUpdated(newIconSize, newBubbleBarPadding)) {
+ return;
+ }
+ mIconScale = 1f;
+ mBubbleBarPadding = newBubbleBarPadding;
+ mIconSize = newIconSize;
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View childView = getChildAt(i);
+ childView.setScaleY(mIconScale);
+ childView.setScaleY(mIconScale);
+ FrameLayout.LayoutParams params = (LayoutParams) childView.getLayoutParams();
+ params.height = (int) mIconSize;
+ params.width = (int) mIconSize;
+ childView.setLayoutParams(params);
+ }
+ mBubbleBarBackground.setBackgroundHeight(getBubbleBarHeight());
+ updateLayoutParams();
+ }
+
+ private float getScaledIconSize() {
+ return mIconSize * mIconScale;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mBubbleBarBounds.left = left;
- mBubbleBarBounds.top = top;
+ mBubbleBarBounds.top = top + mPointerSize;
mBubbleBarBounds.right = right;
mBubbleBarBounds.bottom = bottom;
@@ -193,30 +346,278 @@
setPivotX(mRelativePivotX * getWidth());
setPivotY(mRelativePivotY * getHeight());
- // Position the views
- updateChildrenRenderNodeProperties();
+ if (!mDragging) {
+ // Position the views when not dragging
+ updateBubblesLayoutProperties(mBubbleBarLocation);
+ }
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
- // TODO(b/313661121): set this based on bubble bar position and not LTR or RTL
- boolean onLeft = layoutDirection == LAYOUT_DIRECTION_RTL;
+ if (mBubbleBarLocation == BubbleBarLocation.DEFAULT
+ && mPreviousLayoutDirection != layoutDirection) {
+ Log.d(TAG, "BubbleBar RTL properties changed, new layoutDirection=" + layoutDirection
+ + " previous layoutDirection=" + mPreviousLayoutDirection);
+ mPreviousLayoutDirection = layoutDirection;
+ onBubbleBarLocationChanged();
+ }
+ }
+
+ @SuppressLint("RtlHardcoded")
+ private void onBubbleBarLocationChanged() {
+ final boolean onLeft = mBubbleBarLocation.isOnLeft(isLayoutRtl());
mBubbleBarBackground.setAnchorLeft(onLeft);
mRelativePivotX = onLeft ? 0f : 1f;
+ LayoutParams lp = (LayoutParams) getLayoutParams();
+ lp.gravity = Gravity.BOTTOM | (onLeft ? Gravity.LEFT : Gravity.RIGHT);
+ setLayoutParams(lp); // triggers a relayout
}
/**
- * @return <code>true</code> when bar is pinned to the left edge of the screen
+ * @return current {@link BubbleBarLocation}
*/
- public boolean isOnLeft() {
- return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ public BubbleBarLocation getBubbleBarLocation() {
+ return mBubbleBarLocation;
+ }
+
+ /**
+ * Update {@link BubbleBarLocation}
+ */
+ public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ resetDragAnimation();
+ if (bubbleBarLocation != mBubbleBarLocation) {
+ mBubbleBarLocation = bubbleBarLocation;
+ onBubbleBarLocationChanged();
+ }
+ }
+
+ /**
+ * Set whether this view is currently being dragged
+ */
+ public void setIsDragging(boolean dragging) {
+ if (mDragging == dragging) {
+ return;
+ }
+ mDragging = dragging;
+ setElevation(dragging ? mDragElevation : mBubbleElevation);
+ if (!mDragging) {
+ // Relayout after dragging to ensure that the dragged bubble is positioned correctly
+ requestLayout();
+ }
+ }
+
+ /**
+ * Get translation for bubble bar when drag is released and it needs to animate back to the
+ * resting position.
+ * Resting position is based on the supplied location. If the supplied location is different
+ * from the internal location that was used during bubble bar layout, translation values are
+ * calculated to position the bar at the desired location.
+ *
+ * @param initialTranslation initial bubble bar translation at the start of drag
+ * @param location desired location of the bubble bar when drag is released
+ * @return point with x and y values representing translation on x and y-axis
+ */
+ public PointF getBubbleBarDragReleaseTranslation(PointF initialTranslation,
+ BubbleBarLocation location) {
+ float dragEndTranslationX = initialTranslation.x;
+ if (getBubbleBarLocation().isOnLeft(isLayoutRtl()) != location.isOnLeft(isLayoutRtl())) {
+ // Bubble bar is laid out on left or right side of the screen. And the desired new
+ // location is on the other side. Calculate x translation value required to shift
+ // bubble bar from one side to the other.
+ final float shift = getDistanceFromOtherSide();
+ if (location.isOnLeft(isLayoutRtl())) {
+ // New location is on the left, shift left
+ // before -> |......ooo.| after -> |.ooo......|
+ dragEndTranslationX = -shift;
+ } else {
+ // New location is on the right, shift right
+ // before -> |.ooo......| after -> |......ooo.|
+ dragEndTranslationX = shift;
+ }
+ }
+ return new PointF(dragEndTranslationX, mController.getBubbleBarTranslationY());
+ }
+
+ /**
+ * Get translation for a bubble when drag is released and it needs to animate back to the
+ * resting position.
+ * Resting position is based on the supplied location. If the supplied location is different
+ * from the internal location that was used during bubble bar layout, translation values are
+ * calculated to position the bar at the desired location.
+ *
+ * @param initialTranslation initial bubble translation inside the bar at the start of drag
+ * @param location desired location of the bubble bar when drag is released
+ * @return point with x and y values representing translation on x and y-axis
+ */
+ public PointF getDraggedBubbleReleaseTranslation(PointF initialTranslation,
+ BubbleBarLocation location) {
+ float dragEndTranslationX = initialTranslation.x;
+ boolean newLocationOnLeft = location.isOnLeft(isLayoutRtl());
+ if (getBubbleBarLocation().isOnLeft(isLayoutRtl()) != newLocationOnLeft) {
+ // Calculate translationX based on bar and bubble translations
+ float bubbleBarTx = getBubbleBarDragReleaseTranslation(initialTranslation, location).x;
+ float bubbleTx =
+ getExpandedBubbleTranslationX(
+ indexOfChild(mDraggedBubbleView), getChildCount(), newLocationOnLeft);
+ dragEndTranslationX = bubbleBarTx + bubbleTx;
+ }
+ // translationY does not change during drag and can be reused
+ return new PointF(dragEndTranslationX, initialTranslation.y);
+ }
+
+ private float getDistanceFromOtherSide() {
+ // Calculate the shift needed to position the bubble bar on the other side
+ int displayWidth = getResources().getDisplayMetrics().widthPixels;
+ int margin = 0;
+ if (getLayoutParams() instanceof MarginLayoutParams lp) {
+ margin += lp.leftMargin;
+ margin += lp.rightMargin;
+ }
+ return (float) (displayWidth - getWidth() - margin);
+ }
+
+ /**
+ * Animate bubble bar to the given location transiently. Does not modify the layout or the value
+ * returned by {@link #getBubbleBarLocation()}.
+ */
+ public void animateToBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ if (mBubbleBarLocationAnimator != null && mBubbleBarLocationAnimator.isRunning()) {
+ mBubbleBarLocationAnimator.removeAllListeners();
+ mBubbleBarLocationAnimator.cancel();
+ }
+
+ // Location animation uses two separate animators.
+ // First animator hides the bar.
+ // After it completes, bubble positions in the bar and arrow position is updated.
+ // Second animator is started to show the bar.
+ mBubbleBarLocationAnimator = getLocationUpdateFadeOutAnimator(bubbleBarLocation);
+ mBubbleBarLocationAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateBubblesLayoutProperties(bubbleBarLocation);
+ mBubbleBarBackground.setAnchorLeft(bubbleBarLocation.isOnLeft(isLayoutRtl()));
+
+ // Animate it in
+ mBubbleBarLocationAnimator = getLocationUpdateFadeInAnimator(bubbleBarLocation);
+ mBubbleBarLocationAnimator.start();
+ }
+ });
+ mBubbleBarLocationAnimator.start();
+ }
+
+ private Animator getLocationUpdateFadeOutAnimator(BubbleBarLocation newLocation) {
+ final float shift =
+ getResources().getDisplayMetrics().widthPixels * FADE_OUT_ANIM_POSITION_SHIFT;
+ final boolean onLeft = newLocation.isOnLeft(isLayoutRtl());
+ final float tx = getTranslationX() + (onLeft ? -shift : shift);
+
+ ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, VIEW_TRANSLATE_X, tx)
+ .setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS);
+ positionAnim.setInterpolator(EMPHASIZED_ACCELERATE);
+
+ ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 0f)
+ .setDuration(FADE_OUT_ANIM_ALPHA_DURATION_MS);
+ alphaAnim.setStartDelay(FADE_OUT_ANIM_ALPHA_DELAY_MS);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(positionAnim, alphaAnim);
+ return animatorSet;
+ }
+
+ private Animator getLocationUpdateFadeInAnimator(BubbleBarLocation newLocation) {
+ final float shift =
+ getResources().getDisplayMetrics().widthPixels * FADE_IN_ANIM_POSITION_SHIFT;
+
+ final boolean onLeft = newLocation.isOnLeft(isLayoutRtl());
+ final float startTx;
+ final float finalTx;
+ if (newLocation == mBubbleBarLocation) {
+ // Animated location matches layout location.
+ finalTx = 0;
+ } else {
+ // We are animating in to a transient location, need to move the bar accordingly.
+ finalTx = getDistanceFromOtherSide() * (onLeft ? -1 : 1);
+ }
+ if (onLeft) {
+ // Bar will be shown on the left side. Start point is shifted right.
+ startTx = finalTx + shift;
+ } else {
+ // Bar will be shown on the right side. Start point is shifted left.
+ startTx = finalTx - shift;
+ }
+
+ ValueAnimator positionAnim = new SpringAnimationBuilder(getContext())
+ .setStartValue(startTx)
+ .setEndValue(finalTx)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ .setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS)
+ .build(this, VIEW_TRANSLATE_X);
+
+ ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 1f)
+ .setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(positionAnim, alphaAnim);
+ return animatorSet;
+ }
+
+ /**
+ * Get property that can be used to animate the alpha value for the bar.
+ * When a bubble is being dragged, uses {@link #BUBBLE_DRAG_ALPHA}.
+ * Falls back to {@link com.android.launcher3.LauncherAnimUtils#VIEW_ALPHA} otherwise.
+ */
+ private FloatProperty<? super BubbleBarView> getLocationAnimAlphaProperty() {
+ return mDraggedBubbleView == null ? VIEW_ALPHA : BUBBLE_DRAG_ALPHA;
+ }
+
+ /**
+ * Set alpha value for the bar while a bubble is being dragged.
+ * We can not update the alpha on the bar directly because the dragged bubble would be affected
+ * as well. As it is a child view.
+ * Instead, while a bubble is being dragged, set alpha on each child view, that is not the
+ * dragged view. And set an alpha on the background.
+ * This allows for the dragged bubble to remain visible while the bar is hidden during
+ * animation.
+ */
+ private void setAlphaDuringBubbleDrag(float alpha) {
+ mAlphaDuringDrag = alpha;
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = getChildAt(i);
+ if (view != mDraggedBubbleView) {
+ view.setAlpha(alpha);
+ }
+ }
+ if (mBubbleBarBackground != null) {
+ mBubbleBarBackground.setAlpha((int) (255 * alpha));
+ }
+ }
+
+ private void resetDragAnimation() {
+ if (mBubbleBarLocationAnimator != null) {
+ mBubbleBarLocationAnimator.removeAllListeners();
+ mBubbleBarLocationAnimator.cancel();
+ mBubbleBarLocationAnimator = null;
+ }
+ setAlphaDuringBubbleDrag(1f);
+ setTranslationX(0f);
+ setAlpha(1f);
+ }
+
+ /**
+ * Get bubble bar top coordinate on screen when bar is resting
+ */
+ public int getRestingTopPositionOnScreen() {
+ int displayHeight = DisplayController.INSTANCE.get(getContext()).getInfo().currentSize.y;
+ int bubbleBarHeight = getBubbleBarBounds().height();
+ return displayHeight - bubbleBarHeight + (int) mController.getBubbleBarTranslationY();
}
/**
* Updates the bounds with translation that may have been applied and returns the result.
*/
public Rect getBubbleBarBounds() {
- mBubbleBarBounds.top = getTop() + (int) getTranslationY();
+ mBubbleBarBounds.top = getTop() + (int) getTranslationY() + mPointerSize;
mBubbleBarBounds.bottom = getBottom() + (int) getTranslationY();
return mBubbleBarBounds;
}
@@ -233,6 +634,11 @@
requestLayout();
}
+ /** Like {@link #setRelativePivot(float, float)} but only updates pivot y. */
+ public void setRelativePivotY(float y) {
+ setRelativePivot(mRelativePivotX, y);
+ }
+
/**
* Get current relative pivot for X axis
*/
@@ -247,6 +653,55 @@
return mRelativePivotY;
}
+ /** Notifies the bubble bar that a new bubble animation is starting. */
+ public void onAnimatingBubbleStarted() {
+ mIsAnimatingNewBubble = true;
+ }
+
+ /** Notifies the bubble bar that a new bubble animation is complete. */
+ public void onAnimatingBubbleCompleted() {
+ mIsAnimatingNewBubble = false;
+ }
+
+ /** Add a new bubble to the bubble bar. */
+ public void addBubble(View bubble, FrameLayout.LayoutParams lp) {
+ if (isExpanded()) {
+ // if we're expanded scale the new bubble in
+ bubble.setScaleX(0f);
+ bubble.setScaleY(0f);
+ addView(bubble, 0, lp);
+ createNewBubbleScaleInAnimator(bubble);
+ mNewBubbleScaleInAnimator.start();
+ } else {
+ addView(bubble, 0, lp);
+ }
+ }
+
+ private void createNewBubbleScaleInAnimator(View bubble) {
+ mNewBubbleScaleInAnimator = ValueAnimator.ofFloat(0, 1);
+ mNewBubbleScaleInAnimator.setDuration(SCALE_IN_ANIMATION_DURATION_MS);
+ mNewBubbleScaleInAnimator.addUpdateListener(animation -> {
+ float animatedFraction = animation.getAnimatedFraction();
+ bubble.setScaleX(animatedFraction);
+ bubble.setScaleY(animatedFraction);
+ updateBubblesLayoutProperties(mBubbleBarLocation);
+ invalidate();
+ });
+ mNewBubbleScaleInAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ bubble.setScaleX(1);
+ bubble.setScaleY(1);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateWidth();
+ mNewBubbleScaleInAnimator = null;
+ }
+ });
+ }
+
// TODO: (b/280605790) animate it
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
@@ -257,13 +712,21 @@
}
super.addView(child, index, params);
updateWidth();
+ updateBubbleAccessibilityStates();
+ updateContentDescription();
}
// TODO: (b/283309949) animate it
@Override
public void removeView(View view) {
super.removeView(view);
+ if (view == mSelectedBubbleView) {
+ mSelectedBubbleView = null;
+ mBubbleBarBackground.showArrow(false);
+ }
updateWidth();
+ updateBubbleAccessibilityStates();
+ updateContentDescription();
}
private void updateWidth() {
@@ -272,6 +735,18 @@
setLayoutParams(lp);
}
+ private void updateLayoutParams() {
+ LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ lp.height = (int) getBubbleBarExpandedHeight();
+ lp.width = (int) (mIsBarExpanded ? expandedWidth() : collapsedWidth());
+ setLayoutParams(lp);
+ }
+
+ private float getBubbleBarHeight() {
+ return mIsBarExpanded ? getBubbleBarExpandedHeight()
+ : getBubbleBarCollapsedHeight();
+ }
+
/** @return the horizontal margin between the bubble bar and the edge of the screen. */
int getHorizontalMargin() {
LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
@@ -282,33 +757,41 @@
* Updates the z order, positions, and badge visibility of the bubble views in the bar based
* on the expanded state.
*/
- private void updateChildrenRenderNodeProperties() {
+ private void updateBubblesLayoutProperties(BubbleBarLocation bubbleBarLocation) {
final float widthState = (float) mWidthAnimator.getAnimatedValue();
final float currentWidth = getWidth();
final float expandedWidth = expandedWidth();
final float collapsedWidth = collapsedWidth();
int bubbleCount = getChildCount();
- final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
+ float viewBottom = mBubbleBarBounds.height() + (isExpanded() ? mPointerSize : 0);
+ float bubbleBarAnimatedTop = viewBottom - getBubbleBarHeight();
+ // When translating X & Y the scale is ignored, so need to deduct it from the translations
+ final float ty = bubbleBarAnimatedTop + mBubbleBarPadding - getScaleIconShift();
final boolean animate = getVisibility() == VISIBLE;
- final boolean onLeft = isOnLeft();
+ final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl());
+ // elevation state is opposite to widthState - when expanded all icons are flat
+ float elevationState = (1 - widthState);
for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) getChildAt(i);
- bv.setTranslationY(ty);
-
- // the position of the bubble when the bar is fully expanded
- final float expandedX;
- // the position of the bubble when the bar is fully collapsed
- final float collapsedX;
- if (onLeft) {
- // If bar is on the left, bubbles are ordered right to left
- expandedX = (bubbleCount - i - 1) * (mIconSize + mIconSpacing);
- // Shift the first bubble only if there are more bubbles in addition to overflow
- collapsedX = i == 0 && bubbleCount > 2 ? mIconOverlapAmount : 0;
- } else {
- // Bubbles ordered left to right, don't move the first bubble
- expandedX = i * (mIconSize + mIconSpacing);
- collapsedX = i == 0 ? 0 : mIconOverlapAmount;
+ if (bv == mDraggedBubbleView) {
+ // Skip the dragged bubble. Its translation is managed by the drag controller.
+ continue;
}
+ // Clear out drag translation and offset
+ bv.setDragTranslationX(0f);
+ bv.setOffsetX(0f);
+
+ bv.setScaleX(mIconScale);
+ bv.setScaleY(mIconScale);
+ bv.setTranslationY(ty);
+ // the position of the bubble when the bar is fully expanded
+ final float expandedX = getExpandedBubbleTranslationX(i, bubbleCount, onLeft);
+ // the position of the bubble when the bar is fully collapsed
+ final float collapsedX = getCollapsedBubbleTranslationX(i, bubbleCount, onLeft);
+
+ // slowly animate elevation while keeping correct Z ordering
+ float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i;
+ bv.setZ(fullElevationForChild * elevationState);
if (mIsBarExpanded) {
// If bar is on the right, account for bubble bar expanding and shifting left
@@ -316,10 +799,6 @@
// where the bubble will end up when the animation ends
final float targetX = expandedX + expandedBarShift;
bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
- // if we're fully expanded, set the z level to 0 or to bubble elevation if dragged
- if (widthState == 1f) {
- bv.setZ(bv == mDraggedBubbleView ? mBubbleElevation : 0);
- }
// When we're expanded, we're not stacked so we're not behind the stack
bv.setBehindStack(false, animate);
bv.setAlpha(1);
@@ -328,35 +807,38 @@
final float collapsedBarShift = onLeft ? 0 : currentWidth - collapsedWidth;
final float targetX = collapsedX + collapsedBarShift;
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
- bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
// If we're not the first bubble we're behind the stack
bv.setBehindStack(i > 0, animate);
// If we're fully collapsed, hide all bubbles except for the first 2. If there are
// only 2 bubbles, hide the second bubble as well because it's the overflow.
if (widthState == 0) {
- if (i > 1) {
+ if (i > MAX_VISIBLE_BUBBLES_COLLAPSED - 1) {
bv.setAlpha(0);
- } else if (i == 1 && bubbleCount == 2) {
+ } else if (i == MAX_VISIBLE_BUBBLES_COLLAPSED - 1
+ && bubbleCount == MAX_VISIBLE_BUBBLES_COLLAPSED) {
bv.setAlpha(0);
+ } else {
+ bv.setAlpha(1);
}
}
}
}
// update the arrow position
- final float collapsedArrowPosition = arrowPositionForSelectedWhenCollapsed();
- final float expandedArrowPosition = arrowPositionForSelectedWhenExpanded();
+ final float collapsedArrowPosition = arrowPositionForSelectedWhenCollapsed(
+ bubbleBarLocation);
+ final float expandedArrowPosition = arrowPositionForSelectedWhenExpanded(bubbleBarLocation);
final float interpolatedWidth =
widthState * (expandedWidth - collapsedWidth) + collapsedWidth;
final float arrowPosition;
+
+ float interpolatedShift = (expandedArrowPosition - collapsedArrowPosition) * widthState;
if (onLeft) {
- float interpolatedShift = (expandedArrowPosition - collapsedArrowPosition) * widthState;
arrowPosition = collapsedArrowPosition + interpolatedShift;
} else {
if (mIsBarExpanded) {
- // when the bar is expanding, the selected bubble is always the first, so the arrow
- // always shifts with the interpolated width.
- arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition;
+ arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition
+ + interpolatedShift;
} else {
final float targetPosition = currentWidth - collapsedWidth + collapsedArrowPosition;
arrowPosition =
@@ -364,8 +846,92 @@
}
}
mBubbleBarBackground.setArrowPosition(arrowPosition);
- mBubbleBarBackground.setArrowAlpha((int) (255 * widthState));
+ mBubbleBarBackground.setArrowHeightFraction(widthState);
mBubbleBarBackground.setWidth(interpolatedWidth);
+ mBubbleBarBackground.setBackgroundHeight(getBubbleBarExpandedHeight());
+ }
+
+ private float getScaleIconShift() {
+ return (mIconSize - getScaledIconSize()) / 2;
+ }
+
+ private float getExpandedBubbleTranslationX(int bubbleIndex, int bubbleCount, boolean onLeft) {
+ if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
+ return 0;
+ }
+ final float iconAndSpacing = getScaledIconSize() + mExpandedBarIconsSpacing;
+ float translationX;
+ if (mNewBubbleScaleInAnimator != null && mNewBubbleScaleInAnimator.isRunning()) {
+ translationX = getExpandedBubbleTranslationXDuringScaleAnimation(
+ bubbleIndex, bubbleCount, onLeft);
+ } else if (onLeft) {
+ translationX = mBubbleBarPadding + (bubbleCount - bubbleIndex - 1) * iconAndSpacing;
+ } else {
+ translationX = mBubbleBarPadding + bubbleIndex * iconAndSpacing;
+ }
+ return translationX - getScaleIconShift();
+ }
+
+ /**
+ * Returns the translation X for the bubble at index {@code bubbleIndex} when the bubble bar is
+ * expanded <b>and</b> a new bubble is animating in.
+ *
+ * <p>This method assumes that the animation is running so callers are expected to verify that
+ * before calling it.
+ */
+ private float getExpandedBubbleTranslationXDuringScaleAnimation(
+ int bubbleIndex, int bubbleCount, boolean onLeft) {
+ // when the new bubble scale animation is running, a new bubble is animating in while the
+ // bubble bar is expanded, so we have at least 2 bubbles in the bubble bar - the expanded
+ // one, and the new one animating in.
+
+ if (mNewBubbleScaleInAnimator == null) {
+ // callers of this method are expected to verify that the animation is running, but the
+ // compiler doesn't know that.
+ return 0;
+ }
+ final float iconAndSpacing = getScaledIconSize() + mExpandedBarIconsSpacing;
+ final float newBubbleScale = mNewBubbleScaleInAnimator.getAnimatedFraction();
+ // the new bubble is scaling in from the center, so we need to adjust its translation so
+ // that the distance to the adjacent bubble scales at the same rate.
+ final float pivotAdjustment = -(1 - newBubbleScale) * getScaledIconSize() / 2f;
+
+ if (onLeft) {
+ if (bubbleIndex == 0) {
+ // this is the animating bubble. use scaled spacing between it and the bubble to
+ // its left
+ return (bubbleCount - 1) * getScaledIconSize()
+ + (bubbleCount - 2) * mExpandedBarIconsSpacing
+ + newBubbleScale * mExpandedBarIconsSpacing
+ + pivotAdjustment;
+ }
+ // when the bubble bar is on the left, only the translation of the right-most bubble
+ // is affected by the scale animation.
+ return (bubbleCount - bubbleIndex - 1) * iconAndSpacing;
+ } else if (bubbleIndex == 0) {
+ // the bubble bar is on the right, and this is the animating bubble. it only needs
+ // to be adjusted for the scaling pivot.
+ return pivotAdjustment;
+ } else {
+ return iconAndSpacing * (bubbleIndex - 1 + newBubbleScale);
+ }
+ }
+
+ private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount,
+ boolean onLeft) {
+ if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
+ return 0;
+ }
+ float translationX;
+ if (onLeft) {
+ // Shift the first bubble only if there are more bubbles in addition to overflow
+ translationX = mBubbleBarPadding + (
+ bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
+ ? mIconOverlapAmount : 0);
+ } else {
+ translationX = mBubbleBarPadding + (bubbleIndex == 0 ? 0 : mIconOverlapAmount);
+ }
+ return translationX - getScaleIconShift();
}
/**
@@ -392,7 +958,8 @@
addViewInLayout(child, i, child.getLayoutParams());
}
}
- updateChildrenRenderNodeProperties();
+ updateBubblesLayoutProperties(mBubbleBarLocation);
+ updateContentDescription();
}
}
@@ -401,20 +968,34 @@
mUpdateSelectedBubbleAfterCollapse = updateSelectedBubbleAfterCollapse;
}
+ void setController(Controller controller) {
+ mController = controller;
+ }
+
/**
* Sets which bubble view should be shown as selected.
*/
public void setSelectedBubble(BubbleView view) {
+ BubbleView previouslySelectedBubble = mSelectedBubbleView;
mSelectedBubbleView = view;
- updateArrowForSelected(/* shouldAnimate= */ true);
+ mBubbleBarBackground.showArrow(view != null);
+ // TODO: (b/283309949) remove animation should be implemented first, so than arrow
+ // animation is adjusted, skip animation for now
+ updateArrowForSelected(previouslySelectedBubble != null);
}
/**
* Sets the dragged bubble view to correctly apply Z order. Dragged view should appear on top
*/
public void setDraggedBubble(@Nullable BubbleView view) {
+ if (mDraggedBubbleView != null) {
+ mDraggedBubbleView.setZ(0);
+ }
mDraggedBubbleView = view;
- requestLayout();
+ if (view != null) {
+ view.setZ(mDragElevation);
+ }
+ setIsDragging(view != null);
}
/**
@@ -429,8 +1010,12 @@
return;
}
// Find the center of the bubble when it's expanded, set the arrow position to it.
- final float tx = arrowPositionForSelectedWhenExpanded();
+ final float tx = arrowPositionForSelectedWhenExpanded(mBubbleBarLocation);
final float currentArrowPosition = mBubbleBarBackground.getArrowPositionX();
+ if (tx == currentArrowPosition) {
+ // arrow position remains unchanged
+ return;
+ }
if (shouldAnimate && currentArrowPosition > expandedWidth()) {
Log.d(TAG, "arrow out of bounds of expanded view, skip animation");
shouldAnimate = false;
@@ -450,29 +1035,25 @@
}
}
- private float arrowPositionForSelectedWhenExpanded() {
+ private float arrowPositionForSelectedWhenExpanded(BubbleBarLocation bubbleBarLocation) {
final int index = indexOfChild(mSelectedBubbleView);
- final int bubblePosition;
- if (isOnLeft()) {
- // Bubble positions are reversed. First bubble is on the right.
- bubblePosition = getChildCount() - index - 1;
- } else {
- bubblePosition = index;
- }
- return getPaddingStart() + bubblePosition * (mIconSize + mIconSpacing) + mIconSize / 2f;
+ final float selectedBubbleTranslationX = getExpandedBubbleTranslationX(
+ index, getChildCount(), bubbleBarLocation.isOnLeft(isLayoutRtl()));
+ return selectedBubbleTranslationX + mIconSize / 2f;
}
- private float arrowPositionForSelectedWhenCollapsed() {
+ private float arrowPositionForSelectedWhenCollapsed(BubbleBarLocation bubbleBarLocation) {
final int index = indexOfChild(mSelectedBubbleView);
final int bubblePosition;
- if (isOnLeft()) {
+ if (bubbleBarLocation.isOnLeft(isLayoutRtl())) {
// Bubble positions are reversed. First bubble may be shifted, if there are more
// bubbles than the current bubble and overflow.
- bubblePosition = index == 0 && getChildCount() > 2 ? 1 : 0;
+ bubblePosition = index == 0 && getChildCount() > MAX_VISIBLE_BUBBLES_COLLAPSED ? 1 : 0;
} else {
- bubblePosition = index;
+ bubblePosition = index >= MAX_VISIBLE_BUBBLES_COLLAPSED
+ ? MAX_VISIBLE_BUBBLES_COLLAPSED - 1 : index;
}
- return getPaddingStart() + bubblePosition * (mIconOverlapAmount) + mIconSize / 2f;
+ return mBubbleBarPadding + bubblePosition * (mIconOverlapAmount) + getScaledIconSize() / 2f;
}
@Override
@@ -502,6 +1083,7 @@
} else {
mWidthAnimator.reverse();
}
+ updateBubbleAccessibilityStates();
}
}
@@ -519,18 +1101,39 @@
*/
public float expandedWidth() {
final int childCount = getChildCount();
- final int horizontalPadding = getPaddingStart() + getPaddingEnd();
- return childCount * (mIconSize + mIconSpacing) + horizontalPadding;
+ // spaces amount is less than child count by 1, or 0 if no child views
+ final float totalSpace;
+ final float totalIconSize;
+ if (mNewBubbleScaleInAnimator != null && mNewBubbleScaleInAnimator.isRunning()) {
+ // when this animation is running, a new bubble is animating in while the bubble bar is
+ // expanded, so we have at least 2 bubbles in the bubble bar.
+ final float newBubbleScale = mNewBubbleScaleInAnimator.getAnimatedFraction();
+ totalSpace = (childCount - 2 + newBubbleScale) * mExpandedBarIconsSpacing;
+ totalIconSize = (childCount - 1 + newBubbleScale) * getScaledIconSize();
+ } else {
+ totalSpace = Math.max(childCount - 1, 0) * mExpandedBarIconsSpacing;
+ totalIconSize = childCount * getScaledIconSize();
+ }
+ return totalIconSize + totalSpace + 2 * mBubbleBarPadding;
}
private float collapsedWidth() {
final int childCount = getChildCount();
- final int horizontalPadding = getPaddingStart() + getPaddingEnd();
+ final float horizontalPadding = 2 * mBubbleBarPadding;
// If there are more than 2 bubbles, the first 2 should be visible when collapsed.
// Otherwise just the first bubble should be visible because we don't show the overflow.
- return childCount > 2
- ? mIconSize + mIconOverlapAmount + horizontalPadding
- : mIconSize + horizontalPadding;
+ return childCount > MAX_VISIBLE_BUBBLES_COLLAPSED
+ ? getScaledIconSize() + mIconOverlapAmount + horizontalPadding
+ : getScaledIconSize() + horizontalPadding;
+ }
+
+ private float getBubbleBarExpandedHeight() {
+ return getBubbleBarCollapsedHeight() + mPointerSize;
+ }
+
+ float getBubbleBarCollapsedHeight() {
+ // the pointer is invisible when collapsed
+ return getScaledIconSize() + mBubbleBarPadding * 2;
}
/**
@@ -547,10 +1150,110 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mIsAnimatingNewBubble) {
+ mController.onBubbleBarTouchedWhileAnimating();
+ }
if (!mIsBarExpanded) {
// When the bar is collapsed, all taps on it should expand it.
return true;
}
return super.onInterceptTouchEvent(ev);
}
+
+ /** Whether a new bubble is currently animating. */
+ public boolean isAnimatingNewBubble() {
+ return mIsAnimatingNewBubble;
+ }
+
+
+ private boolean hasOverview() {
+ // Overview is always the last bubble
+ View lastChild = getChildAt(getChildCount() - 1);
+ if (lastChild instanceof BubbleView bubbleView) {
+ return bubbleView.getBubble() instanceof BubbleBarOverflow;
+ }
+ return false;
+ }
+
+ private void updateBubbleAccessibilityStates() {
+ final int childA11y;
+ if (mIsBarExpanded) {
+ // Bar is expanded, focus on the bubbles
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ childA11y = View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+ } else {
+ // Bar is collapsed, only focus on the bar
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ childA11y = View.IMPORTANT_FOR_ACCESSIBILITY_NO;
+ }
+ for (int i = 0; i < getChildCount(); i++) {
+ getChildAt(i).setImportantForAccessibility(childA11y);
+ // Only allowing focusing on bubbles when bar is expanded. Otherwise, in talkback mode,
+ // bubbles can be navigates to in collapsed mode.
+ getChildAt(i).setFocusable(mIsBarExpanded);
+ }
+ }
+
+ private void updateContentDescription() {
+ View firstChild = getChildAt(0);
+ CharSequence contentDesc = firstChild != null ? firstChild.getContentDescription() : "";
+
+ // Don't count overflow if it exists
+ int bubbleCount = getChildCount() - (hasOverview() ? 1 : 0);
+ if (bubbleCount > 1) {
+ contentDesc = getResources().getString(R.string.bubble_bar_description_multiple_bubbles,
+ contentDesc, bubbleCount - 1);
+ }
+ setContentDescription(contentDesc);
+ }
+
+ private boolean isIconSizeOrPaddingUpdated(float newIconSize, float newBubbleBarPadding) {
+ return isIconSizeUpdated(newIconSize) || isPaddingUpdated(newBubbleBarPadding);
+ }
+
+ private boolean isIconSizeUpdated(float newIconSize) {
+ return Float.compare(mIconSize, newIconSize) != 0;
+ }
+
+ private boolean isPaddingUpdated(float newBubbleBarPadding) {
+ return Float.compare(mBubbleBarPadding, newBubbleBarPadding) != 0;
+ }
+
+ private void addAnimationCallBacks(@NonNull ValueAnimator animator,
+ @Nullable Runnable onStart,
+ @Nullable Runnable onEnd,
+ @Nullable ValueAnimator.AnimatorUpdateListener onUpdate) {
+ if (onUpdate != null) animator.addUpdateListener(onUpdate);
+ animator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationCancel(Animator animator) {
+
+ }
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ if (onStart != null) onStart.run();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (onEnd != null) onEnd.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {
+
+ }
+ });
+ }
+
+ /** Interface for BubbleBarView to communicate with its controller. */
+ interface Controller {
+
+ /** Returns the translation Y that the bubble bar should have. */
+ float getBubbleBarTranslationY();
+
+ /** Notifies the controller that the bubble bar was touched while it was animating. */
+ void onBubbleBarTouchedWhileAnimating();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 6bb7b04..eec095d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -18,15 +18,20 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
+import android.content.res.Resources;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
@@ -34,9 +39,11 @@
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarStashController;
+import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.SystemUiProxy;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import java.util.List;
import java.util.Objects;
@@ -48,12 +55,14 @@
*/
public class BubbleBarViewController {
- private static final String TAG = BubbleBarViewController.class.getSimpleName();
-
+ private static final String TAG = "BubbleBarViewController";
+ private static final float APP_ICON_SMALL_DP = 44f;
+ private static final float APP_ICON_MEDIUM_DP = 48f;
+ private static final float APP_ICON_LARGE_DP = 52f;
private final SystemUiProxy mSystemUiProxy;
private final TaskbarActivityContext mActivity;
private final BubbleBarView mBarView;
- private final int mIconSize;
+ private int mIconSize;
// Initialized in init.
private BubbleStashController mBubbleStashController;
@@ -79,13 +88,18 @@
private boolean mHiddenForNoBubbles = true;
private boolean mShouldShowEducation;
+ private BubbleBarViewAnimator mBubbleBarViewAnimator;
+
+ @Nullable
+ private BubbleBarBoundsChangeListener mBoundsChangeListener;
+
public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
mActivity = activity;
mBarView = barView;
mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity);
mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */);
- mBubbleBarAlpha.setUpdateVisibility(true);
- mIconSize = activity.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+ mIconSize = activity.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_icon_size);
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
@@ -94,19 +108,34 @@
mBubbleDragController = bubbleControllers.bubbleDragController;
mTaskbarStashController = controllers.taskbarStashController;
mTaskbarInsetsController = controllers.taskbarInsetsController;
+ mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
- mActivity.addOnDeviceProfileChangeListener(dp ->
- mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight
- );
- mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight;
+ mActivity.addOnDeviceProfileChangeListener(
+ dp -> updateBubbleBarIconSize(dp.taskbarIconSize, /* animate= */ true));
+ updateBubbleBarIconSize(mActivity.getDeviceProfile().taskbarIconSize, /* animate= */ false);
mBubbleBarScale.updateValue(1f);
mBubbleClickListener = v -> onBubbleClicked(v);
mBubbleBarClickListener = v -> onBubbleBarClicked();
mBubbleDragController.setupBubbleBarView(mBarView);
mBarView.setOnClickListener(mBubbleBarClickListener);
- mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
- mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
- );
+ mBarView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
+ if (mBoundsChangeListener != null) {
+ mBoundsChangeListener.onBoundsChanged();
+ }
+ });
+ mBarView.setController(new BubbleBarView.Controller() {
+ @Override
+ public float getBubbleBarTranslationY() {
+ return mBubbleStashController.getBubbleBarTranslationY();
+ }
+
+ @Override
+ public void onBubbleBarTouchedWhileAnimating() {
+ BubbleBarViewController.this.onBubbleBarTouchedWhileAnimating();
+ }
+ });
}
private void onBubbleClicked(View v) {
@@ -114,6 +143,7 @@
if (bubble == null) {
Log.e(TAG, "bubble click listener, bubble was null");
}
+
final String currentlySelected = mBubbleBarController.getSelectedBubbleKey();
if (mBarView.isExpanded() && Objects.equals(bubble.getKey(), currentlySelected)) {
// Tapping the currently selected bubble while expanded collapses the view.
@@ -124,6 +154,11 @@
}
}
+ private void onBubbleBarTouchedWhileAnimating() {
+ mBubbleBarViewAnimator.onBubbleBarTouchedWhileAnimating();
+ mBubbleStashController.onNewBubbleAnimationInterrupted(false, mBarView.getTranslationY());
+ }
+
private void onBubbleBarClicked() {
if (mShouldShowEducation) {
mShouldShowEducation = false;
@@ -135,10 +170,21 @@
// Show user education relative to the reference point
mSystemUiProxy.showUserEducation(position);
} else {
+ // ensure that the bubble bar has the correct translation. we may have just interrupted
+ // the animation by touching the bubble bar.
+ mBubbleBarTranslationY.animateToValue(mBubbleStashController.getBubbleBarTranslationY())
+ .start();
setExpanded(true);
}
}
+ /** Notifies that the stash state is changing. */
+ public void onStashStateChanging() {
+ if (isAnimatingNewBubble()) {
+ mBubbleBarViewAnimator.onStashStateChangingWhileAnimating();
+ }
+ }
+
//
// The below animators are exposed to BubbleStashController so it can manage the stashing
// animation.
@@ -156,6 +202,10 @@
return mBubbleBarTranslationY;
}
+ float getBubbleBarCollapsedHeight() {
+ return mBarView.getBubbleBarCollapsedHeight();
+ }
+
/**
* Whether the bubble bar is visible or not.
*/
@@ -169,12 +219,40 @@
}
/**
+ * @return current {@link BubbleBarLocation}
+ */
+ public BubbleBarLocation getBubbleBarLocation() {
+ return mBarView.getBubbleBarLocation();
+ }
+
+ /**
+ * Update bar {@link BubbleBarLocation}
+ */
+ public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ mBarView.setBubbleBarLocation(bubbleBarLocation);
+ }
+
+ /**
+ * Animate bubble bar to the given location. The location change is transient. It does not
+ * update the state of the bubble bar.
+ * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+ */
+ public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ mBarView.animateToBubbleBarLocation(bubbleBarLocation);
+ }
+
+ /**
* The bounds of the bubble bar.
*/
public Rect getBubbleBarBounds() {
return mBarView.getBubbleBarBounds();
}
+ /** Whether a new bubble is animating. */
+ public boolean isAnimatingNewBubble() {
+ return mBarView.isAnimatingNewBubble();
+ }
+
/** The horizontal margin of the bubble bar from the edge of the screen. */
public int getHorizontalMargin() {
return mBarView.getHorizontalMargin();
@@ -213,6 +291,10 @@
if (mHiddenForNoBubbles != hidden) {
mHiddenForNoBubbles = hidden;
updateVisibilityForStateChange();
+ if (hidden) {
+ mBarView.setAlpha(0);
+ mBarView.setExpanded(false);
+ }
mActivity.bubbleBarVisibilityChanged(!hidden);
}
}
@@ -223,6 +305,11 @@
mBarView.setUpdateSelectedBubbleAfterCollapse(updateSelectedBubbleAfterCollapse);
}
+ /** Returns whether the bubble bar should be hidden because of the current sysui state. */
+ boolean isHiddenForSysui() {
+ return mHiddenForSysui;
+ }
+
/**
* Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen).
*/
@@ -239,8 +326,6 @@
mBarView.setVisibility(VISIBLE);
} else {
mBarView.setVisibility(INVISIBLE);
- mBarView.setAlpha(0);
- mBarView.setExpanded(false);
}
}
@@ -248,6 +333,30 @@
// Modifying view related properties.
//
+ private void updateBubbleBarIconSize(int newIconSize, boolean animate) {
+ Resources res = mActivity.getResources();
+ DisplayMetrics dm = res.getDisplayMetrics();
+ float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ APP_ICON_SMALL_DP, dm);
+ float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ APP_ICON_MEDIUM_DP, dm);
+ float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ APP_ICON_LARGE_DP, dm);
+ float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f;
+ float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
+ mIconSize = newIconSize <= smallMediumThreshold
+ ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) :
+ res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+ float bubbleBarPadding = newIconSize >= mediumLargeThreshold
+ ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) :
+ res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
+ if (animate) {
+ mBarView.animateBubbleBarIconSize(mIconSize, bubbleBarPadding);
+ } else {
+ mBarView.setIconSizeAndPadding(mIconSize, bubbleBarPadding);
+ }
+ }
+
/**
* Sets the translation of the bubble bar during the swipe up gesture.
*/
@@ -288,17 +397,50 @@
/**
* Adds the provided bubble to the bubble bar.
*/
- public void addBubble(BubbleBarItem b) {
+ public void addBubble(BubbleBarItem b, boolean isExpanding, boolean suppressAnimation) {
if (b != null) {
- mBarView.addView(b.getView(), 0,
- new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
+ mBarView.addBubble(
+ b.getView(), new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
b.getView().setOnClickListener(mBubbleClickListener);
mBubbleDragController.setupBubbleView(b.getView());
+
+ if (b instanceof BubbleBarOverflow) {
+ return;
+ }
+
+ if (suppressAnimation || !(b instanceof BubbleBarBubble bubble)) {
+ // the bubble bar and handle are initialized as part of the first bubble animation.
+ // if the animation is suppressed, immediately stash or show the bubble bar to
+ // ensure they've been initialized.
+ if (mTaskbarStashController.isInApp()) {
+ mBubbleStashController.stashBubbleBarImmediate();
+ } else {
+ mBubbleStashController.showBubbleBarImmediate();
+ }
+ return;
+ }
+ animateBubbleNotification(bubble, isExpanding);
} else {
Log.w(TAG, "addBubble, bubble was null!");
}
}
+ /** Animates the bubble bar to notify the user about a bubble change. */
+ public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpanding) {
+ boolean isInApp = mTaskbarStashController.isInApp();
+ // if this is the first bubble, animate to the initial state. one bubble is the overflow
+ // so check for at most 2 children.
+ if (mBarView.getChildCount() <= 2) {
+ mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding);
+ return;
+ }
+
+ // only animate the new bubble if we're in an app and not auto expanding
+ if (isInApp && !isExpanding && !isExpanded()) {
+ mBubbleBarViewAnimator.animateBubbleInForStashed(bubble);
+ }
+ }
+
/**
* Reorders the bubbles based on the provided list.
*/
@@ -357,34 +499,63 @@
* that a bubble is being dragged to dismiss.
* @param bubbleView dragged bubble view
*/
- public void onDragStart(@NonNull BubbleView bubbleView) {
+ public void onBubbleDragStart(@NonNull BubbleView bubbleView) {
if (bubbleView.getBubble() == null) return;
- mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ true);
+
+ mSystemUiProxy.startBubbleDrag(bubbleView.getBubble().getKey());
mBarView.setDraggedBubble(bubbleView);
}
/**
* Notifies SystemUI to expand the selected bubble when the bubble is released.
- * @param bubbleView dragged bubble view
*/
- public void onDragRelease(@NonNull BubbleView bubbleView) {
- if (bubbleView.getBubble() == null) return;
- mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ false);
+ public void onBubbleDragRelease(BubbleBarLocation location) {
+ mSystemUiProxy.stopBubbleDrag(location, mBarView.getRestingTopPositionOnScreen());
}
/**
- * Removes the dragged bubble view in the bubble bar view
+ * Notifies {@link BubbleBarView} that drag and all animations are finished.
*/
- public void onDragEnd() {
+ public void onBubbleDragEnd() {
mBarView.setDraggedBubble(null);
}
+ /** Notifies that dragging the bubble bar ended. */
+ public void onBubbleBarDragEnd() {
+ // we may have changed the bubble bar translation Y value from the value it had at the
+ // beginning of the drag, so update the translation Y animator state
+ mBubbleBarTranslationY.updateValue(mBarView.getTranslationY());
+ }
+
+ /**
+ * Get translation for bubble bar when drag is released.
+ *
+ * @see BubbleBarView#getBubbleBarDragReleaseTranslation(PointF, BubbleBarLocation)
+ */
+ public PointF getBubbleBarDragReleaseTranslation(PointF initialTranslation,
+ BubbleBarLocation location) {
+ return mBarView.getBubbleBarDragReleaseTranslation(initialTranslation, location);
+ }
+
+ /**
+ * Get translation for bubble view when drag is released.
+ *
+ * @see BubbleBarView#getDraggedBubbleReleaseTranslation(PointF, BubbleBarLocation)
+ */
+ public PointF getDraggedBubbleReleaseTranslation(PointF initialTranslation,
+ BubbleBarLocation location) {
+ if (location == mBarView.getBubbleBarLocation()) {
+ return initialTranslation;
+ }
+ return mBarView.getDraggedBubbleReleaseTranslation(initialTranslation, location);
+ }
+
/**
* Called when bubble was dragged into the dismiss target. Notifies System
* @param bubble dismissed bubble item
*/
public void onDismissBubbleWhileDragging(@NonNull BubbleBarItem bubble) {
- mSystemUiProxy.removeBubble(bubble.getKey());
+ mSystemUiProxy.dragBubbleToDismiss(bubble.getKey());
}
/**
@@ -393,4 +564,19 @@
public void onDismissAllBubblesWhileDragging() {
mSystemUiProxy.removeAllBubbles();
}
+
+ /**
+ * Set listener to be notified when bubble bar bounds have changed
+ */
+ public void setBoundsChangeListener(@Nullable BubbleBarBoundsChangeListener listener) {
+ mBoundsChangeListener = listener;
+ }
+
+ /**
+ * Listener to receive updates about bubble bar bounds changing
+ */
+ public interface BubbleBarBoundsChangeListener {
+ /** Called when bounds have changed */
+ void onBoundsChanged();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index c47427d..32d6375 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -29,6 +29,8 @@
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
public final BubbleDragController bubbleDragController;
public final BubbleDismissController bubbleDismissController;
+ public final BubbleBarPinController bubbleBarPinController;
+ public final BubblePinController bubblePinController;
private final RunnableList mPostInitRunnables = new RunnableList();
@@ -43,13 +45,17 @@
BubbleStashController bubbleStashController,
BubbleStashedHandleViewController bubbleStashedHandleViewController,
BubbleDragController bubbleDragController,
- BubbleDismissController bubbleDismissController) {
+ BubbleDismissController bubbleDismissController,
+ BubbleBarPinController bubbleBarPinController,
+ BubblePinController bubblePinController) {
this.bubbleBarController = bubbleBarController;
this.bubbleBarViewController = bubbleBarViewController;
this.bubbleStashController = bubbleStashController;
this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
this.bubbleDragController = bubbleDragController;
this.bubbleDismissController = bubbleDismissController;
+ this.bubbleBarPinController = bubbleBarPinController;
+ this.bubblePinController = bubblePinController;
}
/**
@@ -58,12 +64,15 @@
* in constructors for now, as some controllers may still be waiting for init().
*/
public void init(TaskbarControllers taskbarControllers) {
- bubbleBarController.init(taskbarControllers, this);
+ bubbleBarController.init(this,
+ taskbarControllers.navbarButtonsViewController::isImeVisible);
bubbleBarViewController.init(taskbarControllers, this);
bubbleStashedHandleViewController.init(taskbarControllers, this);
bubbleStashController.init(taskbarControllers, this);
bubbleDragController.init(/* bubbleControllers = */ this);
bubbleDismissController.init(/* bubbleControllers = */ this);
+ bubbleBarPinController.init(this);
+ bubblePinController.init(this);
mPostInitRunnables.executeAllAndDestroy();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
index 73c71c8..a6096e2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
@@ -40,7 +40,7 @@
* @see BubbleDragController
*/
public class BubbleDismissController {
- private static final String TAG = BubbleDismissController.class.getSimpleName();
+ private static final String TAG = "BubbleDismissController";
private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
private final TaskbarActivityContext mActivity;
private final TaskbarDragLayer mDragLayer;
@@ -67,6 +67,9 @@
@Nullable
private BubbleDragAnimator mAnimator;
+ @Nullable
+ private Listener mListener;
+
public BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
mActivity = activity;
mDragLayer = dragLayer;
@@ -82,6 +85,13 @@
}
/**
+ * Set listener to be notified of dismiss events
+ */
+ public void setListener(@Nullable Listener listener) {
+ mListener = listener;
+ }
+
+ /**
* Setup the dismiss view and magnetized object that will be attracted to magnetic target.
* Should be called before handling events or showing/hiding dismiss view.
*
@@ -159,7 +169,8 @@
private void setupMagnetizedObject(@NonNull View magnetizedView) {
mMagnetizedObject = new MagnetizedObject<>(mActivity.getApplicationContext(),
- magnetizedView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
+ magnetizedView, BubbleDragController.DRAG_TRANSLATION_X,
+ DynamicAnimation.TRANSLATION_Y) {
@Override
public float getWidth(@NonNull View underlyingObject) {
return underlyingObject.getWidth() * underlyingObject.getScaleX();
@@ -189,6 +200,9 @@
@NonNull MagnetizedObject<?> draggedObject) {
if (mAnimator == null) return;
mAnimator.animateDismissCaptured();
+ if (mListener != null) {
+ mListener.onStuckToDismissChanged(true /* stuck */);
+ }
}
@Override
@@ -197,6 +211,9 @@
float velX, float velY, boolean wasFlungOut) {
if (mAnimator == null) return;
mAnimator.animateDismissReleased();
+ if (mListener != null) {
+ mListener.onStuckToDismissChanged(false /* stuck */);
+ }
}
@Override
@@ -206,4 +223,10 @@
}
});
}
+
+ /** Interface to receive updates about the dismiss state */
+ public interface Listener {
+ /** Called when view is stuck or unstuck from dismiss target */
+ void onStuckToDismissChanged(boolean stuck);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
index 24dca5e..7aed2d2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
@@ -18,7 +18,6 @@
import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
-import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -30,9 +29,9 @@
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import com.android.launcher3.R;
-import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.bubbles.DismissCircleView;
import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
/**
* The animator performs the bubble animations while dragging and coordinates bubble and dismiss
@@ -42,11 +41,14 @@
private static final float SCALE_BUBBLE_FOCUSED = 1.2f;
private static final float SCALE_BUBBLE_CAPTURED = 0.9f;
private static final float SCALE_BUBBLE_BAR_FOCUSED = 1.1f;
+ // 400f matches to MEDIUM_LOW spring stiffness
+ private static final float TRANSLATION_SPRING_STIFFNESS = 400f;
private final PhysicsAnimator.SpringConfig mDefaultConfig =
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
private final PhysicsAnimator.SpringConfig mTranslationConfig =
- new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_LOW_BOUNCY);
+ new PhysicsAnimator.SpringConfig(TRANSLATION_SPRING_STIFFNESS,
+ DAMPING_RATIO_LOW_BOUNCY);
@NonNull
private final View mView;
@NonNull
@@ -108,25 +110,25 @@
/**
* Animates the dragged bubble movement back to the initial position.
*
- * @param initialPosition the position to animate to
+ * @param restingPosition the position to animate to
* @param velocity the initial velocity to use for the spring animation
* @param endActions gets called when the animation completes or gets cancelled
*/
- public void animateToInitialState(@NonNull PointF initialPosition, @NonNull PointF velocity,
+ public void animateToRestingState(@NonNull PointF restingPosition, @NonNull PointF velocity,
@Nullable Runnable endActions) {
mBubbleAnimator.cancel();
mBubbleAnimator
.spring(DynamicAnimation.SCALE_X, 1f)
.spring(DynamicAnimation.SCALE_Y, 1f)
- .spring(DynamicAnimation.TRANSLATION_X, initialPosition.x, velocity.x,
+ .spring(BubbleDragController.DRAG_TRANSLATION_X, restingPosition.x, velocity.x,
mTranslationConfig)
- .spring(DynamicAnimation.TRANSLATION_Y, initialPosition.y, velocity.y,
+ .spring(DynamicAnimation.TRANSLATION_Y, restingPosition.y, velocity.y,
mTranslationConfig)
.addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
boolean allRelevantPropertyAnimationsEnded) -> {
if (canceled || allRelevantPropertyAnimationsEnded) {
- resetAnimatedViews(initialPosition);
+ resetAnimatedViews(restingPosition);
if (endActions != null) {
endActions.run();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
index 08fd681..efc747c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -24,11 +24,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.wm.shell.common.bubbles.BaseBubblePinController.LocationChangeListener;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
/**
- * Controls bubble bar drag to dismiss interaction.
+ * Controls bubble bar drag interactions.
* Interacts with {@link BubbleDismissController}, used by {@link BubbleBarViewController}.
* Supported interactions:
* - Drag a single bubble view into dismiss target to remove it.
@@ -36,9 +39,43 @@
* Restores initial position of dragged view if released outside of the dismiss target.
*/
public class BubbleDragController {
+
+ /**
+ * Property to update dragged bubble x-translation value.
+ * <p>
+ * When applied to {@link BubbleView}, will use set the translation through
+ * {@link BubbleView#getDragTranslationX()} and {@link BubbleView#setDragTranslationX(float)}
+ * methods.
+ * <p>
+ * When applied to {@link BubbleBarView}, will use {@link View#getTranslationX()} and
+ * {@link View#setTranslationX(float)}.
+ */
+ public static final FloatPropertyCompat<View> DRAG_TRANSLATION_X = new FloatPropertyCompat<>(
+ "dragTranslationX") {
+ @Override
+ public float getValue(View view) {
+ if (view instanceof BubbleView bubbleView) {
+ return bubbleView.getDragTranslationX();
+ }
+ return view.getTranslationX();
+ }
+
+ @Override
+ public void setValue(View view, float value) {
+ if (view instanceof BubbleView bubbleView) {
+ bubbleView.setDragTranslationX(value);
+ } else {
+ view.setTranslationX(value);
+ }
+ }
+ };
+
private final TaskbarActivityContext mActivity;
+ private BubbleBarController mBubbleBarController;
private BubbleBarViewController mBubbleBarViewController;
private BubbleDismissController mBubbleDismissController;
+ private BubbleBarPinController mBubbleBarPinController;
+ private BubblePinController mBubblePinController;
public BubbleDragController(TaskbarActivityContext activity) {
mActivity = activity;
@@ -50,8 +87,18 @@
* controllers may still be waiting for init().
*/
public void init(@NonNull BubbleControllers bubbleControllers) {
+ mBubbleBarController = bubbleControllers.bubbleBarController;
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleDismissController = bubbleControllers.bubbleDismissController;
+ mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
+ mBubblePinController = bubbleControllers.bubblePinController;
+ mBubbleDismissController.setListener(
+ stuck -> {
+ if (stuck) {
+ mBubbleBarPinController.onStuckToDismissTarget();
+ mBubblePinController.onStuckToDismissTarget();
+ }
+ });
}
/**
@@ -65,19 +112,61 @@
}
bubbleView.setOnTouchListener(new BubbleTouchListener() {
+
+ private BubbleBarLocation mReleasedLocation = BubbleBarLocation.DEFAULT;
+
+ private final LocationChangeListener mLocationChangeListener =
+ new LocationChangeListener() {
+ @Override
+ public void onChange(@NonNull BubbleBarLocation location) {
+ mBubbleBarController.animateBubbleBarLocation(location);
+ }
+
+ @Override
+ public void onRelease(@NonNull BubbleBarLocation location) {
+ mReleasedLocation = location;
+ }
+ };
+
@Override
void onDragStart() {
- mBubbleBarViewController.onDragStart(bubbleView);
+ mBubblePinController.setListener(mLocationChangeListener);
+ mBubbleBarViewController.onBubbleDragStart(bubbleView);
+ mBubblePinController.onDragStart(
+ mBubbleBarViewController.getBubbleBarLocation().isOnLeft(
+ bubbleView.isLayoutRtl()));
}
@Override
- void onDragEnd() {
- mBubbleBarViewController.onDragEnd();
+ protected void onDragUpdate(float x, float y, float newTx, float newTy) {
+ bubbleView.setDragTranslationX(newTx);
+ bubbleView.setTranslationY(newTy);
+ mBubblePinController.onDragUpdate(x, y);
}
@Override
protected void onDragRelease() {
- mBubbleBarViewController.onDragRelease(bubbleView);
+ mBubblePinController.onDragEnd();
+ mBubbleBarViewController.onBubbleDragRelease(mReleasedLocation);
+ }
+
+ @Override
+ protected void onDragDismiss() {
+ mBubblePinController.onDragEnd();
+ mBubbleBarViewController.onBubbleDragEnd();
+ }
+
+ @Override
+ void onDragEnd() {
+ mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
+ mBubbleBarViewController.onBubbleDragEnd();
+ mBubblePinController.setListener(null);
+ }
+
+ @Override
+ protected PointF getRestingPosition() {
+ return mBubbleBarViewController.getDraggedBubbleReleaseTranslation(
+ getInitialPosition(), mReleasedLocation);
}
});
}
@@ -89,6 +178,12 @@
public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
PointF initialRelativePivot = new PointF();
bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
+
+ private BubbleBarLocation mReleasedLocation = BubbleBarLocation.DEFAULT;
+
+ private final LocationChangeListener mLocationChangeListener =
+ location -> mReleasedLocation = location;
+
@Override
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
if (bubbleBarView.isExpanded()) return false;
@@ -97,17 +192,49 @@
@Override
void onDragStart() {
+ mBubbleBarPinController.setListener(mLocationChangeListener);
initialRelativePivot.set(bubbleBarView.getRelativePivotX(),
bubbleBarView.getRelativePivotY());
// By default the bubble bar view pivot is in bottom right corner, while dragging
// it should be centered in order to align it with the dismiss target view
bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
+ bubbleBarView.setIsDragging(true);
+ mBubbleBarPinController.onDragStart(
+ bubbleBarView.getBubbleBarLocation().isOnLeft(bubbleBarView.isLayoutRtl()));
+ }
+
+ @Override
+ protected void onDragUpdate(float x, float y, float newTx, float newTy) {
+ bubbleBarView.setTranslationX(newTx);
+ bubbleBarView.setTranslationY(newTy);
+ mBubbleBarPinController.onDragUpdate(x, y);
+ }
+
+ @Override
+ protected void onDragRelease() {
+ mBubbleBarPinController.onDragEnd();
+ }
+
+ @Override
+ protected void onDragDismiss() {
+ mBubbleBarPinController.onDragEnd();
}
@Override
void onDragEnd() {
+ // Make sure to update location as the first thing. Pivot update causes a relayout
+ mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
+ bubbleBarView.setIsDragging(false);
// Restoring the initial pivot for the bubble bar view
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
+ mBubbleBarViewController.onBubbleBarDragEnd();
+ mBubbleBarPinController.setListener(null);
+ }
+
+ @Override
+ protected PointF getRestingPosition() {
+ return mBubbleBarViewController.getBubbleBarDragReleaseTranslation(
+ getInitialPosition(), mReleasedLocation);
}
});
}
@@ -170,6 +297,12 @@
abstract void onDragStart();
/**
+ * Called when bubble is dragged to new coordinates.
+ * Not called while bubble is stuck to the dismiss target.
+ */
+ protected abstract void onDragUpdate(float x, float y, float newTx, float newTy);
+
+ /**
* Called when the dragging interaction has ended and all the animations have completed
*/
abstract void onDragEnd();
@@ -188,6 +321,20 @@
protected void onDragDismiss() {
}
+ /**
+ * Get the initial position of the view when drag started
+ */
+ protected PointF getInitialPosition() {
+ return mViewInitialPosition;
+ }
+
+ /**
+ * Get the resting position of the view when drag is released
+ */
+ protected PointF getRestingPosition() {
+ return mViewInitialPosition;
+ }
+
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) {
@@ -232,8 +379,10 @@
* @param event the motion event
*/
protected void onTouchMove(@NonNull View view, @NonNull MotionEvent event) {
- final float dx = event.getRawX() - mTouchDownLocation.x;
- final float dy = event.getRawY() - mTouchDownLocation.y;
+ float rawX = event.getRawX();
+ float rawY = event.getRawY();
+ final float dx = rawX - mTouchDownLocation.x;
+ final float dy = rawY - mTouchDownLocation.y;
switch (mState) {
case TOUCHED:
final boolean movedOut = Math.hypot(dx, dy) > mTouchSlop;
@@ -244,7 +393,7 @@
}
break;
case DRAGGING:
- drag(view, event, dx, dy);
+ drag(view, event, dx, dy, rawX, rawY);
break;
}
}
@@ -293,10 +442,12 @@
mBubbleDismissController.showDismissView();
}
- private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy) {
+ private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy,
+ float x, float y) {
if (mBubbleDismissController.handleTouchEvent(event)) return;
- view.setTranslationX(mViewInitialPosition.x + dx);
- view.setTranslationY(mViewInitialPosition.y + dy);
+ final float newTx = mViewInitialPosition.x + dx;
+ final float newTy = mViewInitialPosition.y + dy;
+ onDragUpdate(x, y, newTx, newTy);
}
private void stopDragging(@NonNull View view, @NonNull MotionEvent event) {
@@ -311,7 +462,7 @@
mAnimator.animateDismiss(mViewInitialPosition, onComplete);
} else {
onDragRelease();
- mAnimator.animateToInitialState(mViewInitialPosition, getCurrentVelocity(),
+ mAnimator.animateToRestingState(getRestingPosition(), getCurrentVelocity(),
onComplete);
}
mBubbleDismissController.hideDismissView();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt
new file mode 100644
index 0000000..a77e685
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Point
+import android.view.Gravity.BOTTOM
+import android.view.Gravity.LEFT
+import android.view.Gravity.RIGHT
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.R
+import com.android.wm.shell.common.bubbles.BaseBubblePinController
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
+
+/** Controller to manage pinning bubble bar to left or right when dragging starts from a bubble */
+class BubblePinController(
+ private val context: Context,
+ private val container: FrameLayout,
+ screenSizeProvider: () -> Point
+) : BaseBubblePinController(screenSizeProvider) {
+
+ var dropTargetSize: Point? = null
+
+ private lateinit var bubbleBarViewController: BubbleBarViewController
+ private lateinit var bubbleStashController: BubbleStashController
+ private var exclRectWidth: Float = 0f
+ private var exclRectHeight: Float = 0f
+
+ private var dropTargetView: View? = null
+ // Fallback width and height in case shell has not sent the size over
+ private var dropTargetDefaultWidth: Int = 0
+ private var dropTargetDefaultHeight: Int = 0
+ private var dropTargetMargin: Int = 0
+
+ fun init(bubbleControllers: BubbleControllers) {
+ bubbleBarViewController = bubbleControllers.bubbleBarViewController
+ bubbleStashController = bubbleControllers.bubbleStashController
+ exclRectWidth = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width)
+ exclRectHeight = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_height)
+ dropTargetDefaultWidth =
+ context.resources.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_drop_target_default_width
+ )
+ dropTargetDefaultHeight =
+ context.resources.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_drop_target_default_height
+ )
+ dropTargetMargin =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_drop_target_margin)
+ }
+
+ override fun getExclusionRectWidth(): Float {
+ return exclRectWidth
+ }
+
+ override fun getExclusionRectHeight(): Float {
+ return exclRectHeight
+ }
+
+ override fun getDropTargetView(): View? {
+ return dropTargetView
+ }
+
+ override fun removeDropTargetView(view: View) {
+ container.removeView(view)
+ dropTargetView = null
+ }
+
+ override fun createDropTargetView(): View {
+ return LayoutInflater.from(context)
+ .inflate(R.layout.bubble_expanded_view_drop_target, container, false)
+ .also { view ->
+ dropTargetView = view
+ container.addView(view)
+ }
+ }
+
+ @SuppressLint("RtlHardcoded")
+ override fun updateLocation(location: BubbleBarLocation) {
+ val onLeft = location.isOnLeft(container.isLayoutRtl)
+
+ val bubbleBarBounds = bubbleBarViewController.bubbleBarBounds
+ dropTargetView?.updateLayoutParams<FrameLayout.LayoutParams> {
+ gravity = BOTTOM or (if (onLeft) LEFT else RIGHT)
+ width = dropTargetSize?.x ?: dropTargetDefaultWidth
+ height = dropTargetSize?.y ?: dropTargetDefaultHeight
+ bottomMargin =
+ -bubbleStashController.bubbleBarTranslationY.toInt() +
+ bubbleBarBounds.height() +
+ dropTargetMargin
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 09021ed..74ddf90 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -23,7 +23,9 @@
import android.annotation.Nullable;
import android.view.InsetsController;
import android.view.MotionEvent;
+import android.view.View;
+import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.StashedHandleViewController;
import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -31,6 +33,8 @@
import com.android.launcher3.taskbar.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
/**
* Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to
@@ -38,7 +42,7 @@
*/
public class BubbleStashController {
- private static final String TAG = BubbleStashController.class.getSimpleName();
+ private static final String TAG = "BubbleStashController";
/**
* How long to stash/unstash.
@@ -74,11 +78,16 @@
private boolean mBubblesShowingOnOverview;
private boolean mIsSysuiLocked;
+ private final float mHandleCenterFromScreenBottom;
+
@Nullable
private AnimatorSet mAnimator;
public BubbleStashController(TaskbarActivityContext activity) {
mActivity = activity;
+ // the handle is centered within the stashed taskbar area
+ mHandleCenterFromScreenBottom =
+ mActivity.getResources().getDimensionPixelSize(R.dimen.bubblebar_stashed_size) / 2f;
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
@@ -114,21 +123,17 @@
}
/**
- * Animates the bubble bar and handle to their initial state, transitioning from the state where
- * both views are invisible. Called when the first bubble is added or when the device is
+ * Animates the handle (or bubble bar depending on state) to be visible after the device is
* unlocked.
*
* <p>Normally either the bubble bar or the handle is visible,
* and {@link #showBubbleBar(boolean)} and {@link #stashBubbleBar()} are used to transition
* between these two states. But the transition from the state where both the bar and handle
* are invisible is slightly different.
- *
- * <p>The initial state will depend on the current state of the device, i.e. overview, home etc
- * and whether bubbles are requested to be expanded.
*/
- public void animateToInitialState(boolean expanding) {
+ private void animateAfterUnlock() {
AnimatorSet animatorSet = new AnimatorSet();
- if (expanding || mBubblesShowingOnHome || mBubblesShowingOnOverview) {
+ if (mBubblesShowingOnHome || mBubblesShowingOnOverview) {
mIsStashed = false;
animatorSet.playTogether(mIconScaleForStash.animateToValue(1),
mIconTranslationYForStash.animateToValue(getBubbleBarTranslationY()),
@@ -208,7 +213,7 @@
if (isSysuiLocked != mIsSysuiLocked) {
mIsSysuiLocked = isSysuiLocked;
if (!mIsSysuiLocked && mBarViewController.hasBubbles()) {
- animateToInitialState(false /* expanding */);
+ animateAfterUnlock();
}
}
}
@@ -241,6 +246,11 @@
&& !mBubblesShowingOnHome
&& !mBubblesShowingOnOverview;
if (mIsStashed != isStashed) {
+ // notify the view controller that the stash state is about to change so that it can
+ // cancel an ongoing animation if there is one.
+ // note that this has to be called before updating mIsStashed with the new value,
+ // otherwise interrupting an ongoing animation may update it again with the wrong state
+ mBarViewController.onStashStateChanging();
mIsStashed = isStashed;
if (mAnimator != null) {
mAnimator.cancel();
@@ -263,7 +273,6 @@
*/
private AnimatorSet createStashAnimator(boolean isStashed, long duration) {
AnimatorSet animatorSet = new AnimatorSet();
- final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
// Not exactly half and may overlap. See [first|second]HalfDurationScale below.
@@ -277,7 +286,8 @@
firstHalfDurationScale = 0.75f;
secondHalfDurationScale = 0.5f;
- fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation));
+ fullLengthAnimatorSet.play(
+ mIconTranslationYForStash.animateToValue(getStashTranslation()));
firstHalfAnimatorSet.playTogether(
mIconAlphaForStash.animateToValue(0),
@@ -326,6 +336,10 @@
return animatorSet;
}
+ private float getStashTranslation() {
+ return (mUnstashedHeight - mStashedHeight) / 2f;
+ }
+
private void onIsStashedChanged() {
mControllers.runAfterInit(() -> {
mHandleViewController.onIsStashedChanged();
@@ -333,7 +347,7 @@
});
}
- private float getBubbleBarTranslationYForTaskbar() {
+ public float getBubbleBarTranslationYForTaskbar() {
return -mActivity.getDeviceProfile().taskbarBottomMargin;
}
@@ -344,7 +358,7 @@
hotseatCellHeight - mUnstashedHeight) / 2;
}
- float getBubbleBarTranslationY() {
+ public float getBubbleBarTranslationY() {
// If we're on home, adjust the translation so the bubble bar aligns with hotseat.
// Otherwise we're either showing in an app or in overview. In either case adjust it so
// the bubble bar aligns with the taskbar.
@@ -352,8 +366,93 @@
: getBubbleBarTranslationYForTaskbar();
}
+ /**
+ * The difference on the Y axis between the center of the handle and the center of the bubble
+ * bar.
+ */
+ public float getDiffBetweenHandleAndBarCenters() {
+ // the difference between the centers of the handle and the bubble bar is the difference
+ // between their distance from the bottom of the screen.
+
+ float barCenter = mBarViewController.getBubbleBarCollapsedHeight() / 2f;
+ return mHandleCenterFromScreenBottom - barCenter;
+ }
+
+ /** The distance the handle moves as part of the new bubble animation. */
+ public float getStashedHandleTranslationForNewBubbleAnimation() {
+ // the should move up to the top of the stashed taskbar area. it is centered within it so
+ // it should move the same distance as it is away from the bottom.
+ return -mHandleCenterFromScreenBottom;
+ }
+
/** Checks whether the motion event is over the stash handle. */
public boolean isEventOverStashHandle(MotionEvent ev) {
return mHandleViewController.isEventOverHandle(ev);
}
+
+ /** Set a bubble bar location */
+ public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ mHandleViewController.setBubbleBarLocation(bubbleBarLocation);
+ }
+
+ /** Returns the [PhysicsAnimator] for the stashed handle view. */
+ public PhysicsAnimator<View> getStashedHandlePhysicsAnimator() {
+ return mHandleViewController.getPhysicsAnimator();
+ }
+
+ /** Notifies taskbar that it should update its touchable region. */
+ public void updateTaskbarTouchRegion() {
+ mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
+ }
+
+ /** Shows the bubble bar immediately without animation. */
+ public void showBubbleBarImmediate() {
+ mHandleViewController.setTranslationYForSwipe(0);
+ mIconTranslationYForStash.updateValue(getBubbleBarTranslationY());
+ mIconAlphaForStash.setValue(1);
+ mIconScaleForStash.updateValue(1);
+ mIsStashed = false;
+ onIsStashedChanged();
+ }
+
+ /** Stashes the bubble bar immediately without animation. */
+ public void stashBubbleBarImmediate() {
+ mHandleViewController.setTranslationYForSwipe(0);
+ mBubbleStashedHandleAlpha.setValue(1);
+ mIconAlphaForStash.setValue(0);
+ mIconTranslationYForStash.updateValue(getStashTranslation());
+ mIconScaleForStash.updateValue(STASHED_BAR_SCALE);
+ mIsStashed = true;
+ onIsStashedChanged();
+ }
+
+ /**
+ * Updates the values of the internal animators after the new bubble animation was interrupted
+ *
+ * @param isStashed whether the current state should be stashed
+ * @param bubbleBarTranslationY the current bubble bar translation. this is only used if the
+ * bubble bar is showing to ensure that the stash animator runs
+ * smoothly.
+ */
+ public void onNewBubbleAnimationInterrupted(boolean isStashed, float bubbleBarTranslationY) {
+ if (isStashed) {
+ mBubbleStashedHandleAlpha.setValue(1);
+ mIconAlphaForStash.setValue(0);
+ mIconScaleForStash.updateValue(STASHED_BAR_SCALE);
+ mIconTranslationYForStash.updateValue(getStashTranslation());
+ } else {
+ mBubbleStashedHandleAlpha.setValue(0);
+ mHandleViewController.setTranslationYForSwipe(0);
+ mIconAlphaForStash.setValue(1);
+ mIconScaleForStash.updateValue(1);
+ mIconTranslationYForStash.updateValue(bubbleBarTranslationY);
+ }
+ mIsStashed = isStashed;
+ onIsStashedChanged();
+ }
+
+ /** Set the translation Y for the stashed handle. */
+ public void setHandleTranslationY(float ty) {
+ mHandleViewController.setTranslationYForSwipe(ty);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index f88460f..91103d7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar.bubbles;
import static android.view.View.INVISIBLE;
-import static android.view.View.LAYOUT_DIRECTION_RTL;
import static android.view.View.VISIBLE;
import android.animation.Animator;
@@ -39,6 +38,8 @@
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
/**
* Handles properties/data collection, then passes the results to our stashed handle View to render.
@@ -47,7 +48,7 @@
private final TaskbarActivityContext mActivity;
private final StashedHandleView mStashedHandleView;
- private final MultiValueAlpha mTaskbarStashedHandleAlpha;
+ private final MultiValueAlpha mStashedHandleAlpha;
// Initialized in init.
private BubbleBarViewController mBarViewController;
@@ -75,7 +76,7 @@
StashedHandleView stashedHandleView) {
mActivity = activity;
mStashedHandleView = stashedHandleView;
- mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
+ mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
@@ -93,7 +94,7 @@
R.dimen.transient_taskbar_bottom_margin);
mStashedHandleView.getLayoutParams().height = mBarSize + bottomMargin;
- mTaskbarStashedHandleAlpha.get(0).setValue(0);
+ mStashedHandleAlpha.get(0).setValue(0);
mStashedTaskbarHeight = resources.getDimensionPixelSize(
R.dimen.bubblebar_stashed_size);
@@ -119,14 +120,19 @@
}, Executors.UI_HELPER_EXECUTOR);
mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
- updateBounds());
+ updateBounds(mBarViewController.getBubbleBarLocation()));
}
- private void updateBounds() {
+ /** Returns the [PhysicsAnimator] for the stashed handle view. */
+ public PhysicsAnimator<View> getPhysicsAnimator() {
+ return PhysicsAnimator.getInstance(mStashedHandleView);
+ }
+
+ private void updateBounds(BubbleBarLocation bubbleBarLocation) {
// As more bubbles get added, the icon bounds become larger. To ensure a consistent
// handle bar position, we pin it to the edge of the screen.
final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2;
- if (isOnLeft()) {
+ if (bubbleBarLocation.isOnLeft(mStashedHandleView.isLayoutRtl())) {
final int left = mBarViewController.getHorizontalMargin();
mStashedHandleBounds.set(
left,
@@ -149,11 +155,6 @@
mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f);
}
- private boolean isOnLeft() {
- // TODO(b/313661121): set this based on bubble bar position and not LTR or RTL
- return mStashedHandleView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- }
-
public void onDestroy() {
mRegionSamplingHelper.stopAndDestroy();
mRegionSamplingHelper = null;
@@ -247,7 +248,7 @@
* Used by {@link BubbleStashController} to animate the handle when stashing or un stashing.
*/
public MultiPropertyFactory<View> getStashedHandleAlpha() {
- return mTaskbarStashedHandleAlpha;
+ return mStashedHandleAlpha;
}
/**
@@ -301,4 +302,9 @@
public boolean containsX(int x) {
return x >= mStashedHandleBounds.left && x <= mStashedHandleBounds.right;
}
+
+ /** Set a bubble bar location */
+ public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ updateBounds(bubbleBarLocation);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 6549ad6..2f92fbb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -21,6 +21,7 @@
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Rect;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -66,6 +67,9 @@
private final ImageView mAppIcon;
private final int mBubbleSize;
+ private float mDragTranslationX;
+ private float mOffsetX;
+
private DotRenderer mDotRenderer;
private DotRenderer.DrawParams mDrawParams;
private int mDotColor;
@@ -126,6 +130,39 @@
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
+ /**
+ * Set translation-x while this bubble is being dragged.
+ * Translation applied to the view is a sum of {@code translationX} and offset defined by
+ * {@link #setOffsetX(float)}.
+ */
+ public void setDragTranslationX(float translationX) {
+ mDragTranslationX = translationX;
+ applyDragTranslation();
+ }
+
+ /**
+ * Get translation value applied via {@link #setDragTranslationX(float)}.
+ */
+ public float getDragTranslationX() {
+ return mDragTranslationX;
+ }
+
+ /**
+ * Set offset on x-axis while dragging.
+ * Used to counter parent translation in order to keep the dragged view at the current position
+ * on screen.
+ * Translation applied to the view is a sum of {@code offsetX} and translation defined by
+ * {@link #setDragTranslationX(float)}
+ */
+ public void setOffsetX(float offsetX) {
+ mOffsetX = offsetX;
+ applyDragTranslation();
+ }
+
+ private void applyDragTranslation() {
+ setTranslationX(mDragTranslationX + mOffsetX);
+ }
+
@Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
@@ -145,12 +182,22 @@
}
/** Sets the bubble being rendered in this view. */
- void setBubble(BubbleBarBubble bubble) {
+ public void setBubble(BubbleBarBubble bubble) {
mBubble = bubble;
mBubbleIcon.setImageBitmap(bubble.getIcon());
mAppIcon.setImageBitmap(bubble.getBadge());
mDotColor = bubble.getDotColor();
mDotRenderer = new DotRenderer(mBubbleSize, bubble.getDotPath(), DEFAULT_PATH_SIZE);
+ String contentDesc = bubble.getInfo().getTitle();
+ if (TextUtils.isEmpty(contentDesc)) {
+ contentDesc = getResources().getString(R.string.bubble_bar_bubble_fallback_description);
+ }
+ String appName = bubble.getInfo().getAppName();
+ if (!TextUtils.isEmpty(appName)) {
+ contentDesc = getResources().getString(R.string.bubble_bar_bubble_description,
+ contentDesc, appName);
+ }
+ setContentDescription(contentDesc);
}
/**
@@ -159,7 +206,7 @@
* the list of bubbles. It doesn't show an app icon because it is part of system UI / doesn't
* come from an app.
*/
- void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
+ public void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
mBubble = overflow;
mBubbleIcon.setImageBitmap(bitmap);
mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
@@ -168,7 +215,7 @@
/** Returns the bubble being rendered in this view. */
@Nullable
- BubbleBarItem getBubble() {
+ public BubbleBarItem getBubble() {
return mBubble;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS b/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
index edabae2..3f947a0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
@@ -1,3 +1,5 @@
[email protected]
[email protected]
[email protected]
[email protected]
+
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
new file mode 100644
index 0000000..d88e272
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles.animation
+
+import android.view.View
+import android.view.View.VISIBLE
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
+import com.android.launcher3.taskbar.bubbles.BubbleBarView
+import com.android.launcher3.taskbar.bubbles.BubbleStashController
+import com.android.launcher3.taskbar.bubbles.BubbleView
+import com.android.wm.shell.shared.animation.PhysicsAnimator
+
+/** Handles animations for bubble bar bubbles. */
+class BubbleBarViewAnimator
+@JvmOverloads
+constructor(
+ private val bubbleBarView: BubbleBarView,
+ private val bubbleStashController: BubbleStashController,
+ private val scheduler: Scheduler = HandlerScheduler(bubbleBarView)
+) {
+
+ private var animatingBubble: AnimatingBubble? = null
+
+ private companion object {
+ /** The time to show the flyout. */
+ const val FLYOUT_DELAY_MS: Long = 2500
+ /** The initial scale Y value that the new bubble is set to before the animation starts. */
+ const val BUBBLE_ANIMATION_INITIAL_SCALE_Y = 0.3f
+ /** The minimum alpha value to make the bubble bar touchable. */
+ const val MIN_ALPHA_FOR_TOUCHABLE = 0.5f
+ }
+
+ /** Wrapper around the animating bubble with its show and hide animations. */
+ private data class AnimatingBubble(
+ val bubbleView: BubbleView,
+ val showAnimation: Runnable,
+ val hideAnimation: Runnable
+ )
+
+ /** An interface for scheduling jobs. */
+ interface Scheduler {
+
+ /** Schedule the given [block] to run. */
+ fun post(block: Runnable)
+
+ /** Schedule the given [block] to start with a delay of [delayMillis]. */
+ fun postDelayed(delayMillis: Long, block: Runnable)
+
+ /** Cancel the given [block] if it hasn't started yet. */
+ fun cancel(block: Runnable)
+ }
+
+ /** A [Scheduler] that uses a Handler to run jobs. */
+ private class HandlerScheduler(private val view: View) : Scheduler {
+
+ override fun post(block: Runnable) {
+ view.post(block)
+ }
+
+ override fun postDelayed(delayMillis: Long, block: Runnable) {
+ view.postDelayed(block, delayMillis)
+ }
+
+ override fun cancel(block: Runnable) {
+ view.removeCallbacks(block)
+ }
+ }
+
+ private val springConfig =
+ PhysicsAnimator.SpringConfig(
+ stiffness = SpringForce.STIFFNESS_LOW,
+ dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
+ )
+
+ /** Animates a bubble for the state where the bubble bar is stashed. */
+ fun animateBubbleInForStashed(b: BubbleBarBubble) {
+ val bubbleView = b.view
+ val animator = PhysicsAnimator.getInstance(bubbleView)
+ if (animator.isRunning()) animator.cancel()
+ // the animation of a new bubble is divided into 2 parts. The first part shows the bubble
+ // and the second part hides it after a delay.
+ val showAnimation = buildHandleToBubbleBarAnimation()
+ val hideAnimation = buildBubbleBarToHandleAnimation()
+ animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
+ scheduler.post(showAnimation)
+ scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
+ }
+
+ /**
+ * Returns a [Runnable] that starts the animation that morphs the handle to the bubble bar.
+ *
+ * Visually, the animation is divided into 2 parts. The stash handle starts animating up and
+ * fading out and then the bubble bar starts animating up and fading in.
+ *
+ * To make the transition from the handle to the bar smooth, the positions and movement of the 2
+ * views must be synchronized. To do that we use a single spring path along the Y axis, starting
+ * from the handle's position to the eventual bar's position. The path is split into 3 parts.
+ * 1. In the first part, we only animate the handle.
+ * 2. In the second part the handle is fully hidden, and the bubble bar is animating in.
+ * 3. The third part is the overshoot of the spring animation, where we make the bubble fully
+ * visible which helps avoiding further updates when we re-enter the second part.
+ */
+ private fun buildHandleToBubbleBarAnimation() = Runnable {
+ // prepare the bubble bar for the animation
+ bubbleBarView.onAnimatingBubbleStarted()
+ bubbleBarView.visibility = VISIBLE
+ bubbleBarView.alpha = 0f
+ bubbleBarView.translationY = 0f
+ bubbleBarView.scaleX = 1f
+ bubbleBarView.scaleY = BUBBLE_ANIMATION_INITIAL_SCALE_Y
+ bubbleBarView.relativePivotY = 0.5f
+
+ // this is the offset between the center of the bubble bar and the center of the stash
+ // handle. when the handle becomes invisible and we start animating in the bubble bar,
+ // the translation y is offset by this value to make the transition from the handle to the
+ // bar smooth.
+ val offset = bubbleStashController.diffBetweenHandleAndBarCenters
+ val stashedHandleTranslationY =
+ bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
+
+ // this is the total distance that both the stashed handle and the bubble will be traveling
+ // at the end of the animation the bubble bar will be positioned in the same place when it
+ // shows while we're in an app.
+ val totalTranslationY = bubbleStashController.bubbleBarTranslationYForTaskbar + offset
+ val animator = bubbleStashController.stashedHandlePhysicsAnimator
+ animator.setDefaultSpringConfig(springConfig)
+ animator.spring(DynamicAnimation.TRANSLATION_Y, totalTranslationY)
+ animator.addUpdateListener { handle, values ->
+ val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener
+ when {
+ ty >= stashedHandleTranslationY -> {
+ // we're in the first leg of the animation. only animate the handle. the bubble
+ // bar remains hidden during this part of the animation
+
+ // map the path [0, stashedHandleTranslationY] to [0,1]
+ val fraction = ty / stashedHandleTranslationY
+ handle.alpha = 1 - fraction
+ }
+ ty >= totalTranslationY -> {
+ // this is the second leg of the animation. the handle should be completely
+ // hidden and the bubble bar should start animating in.
+ // it's possible that we're re-entering this leg because this is a spring
+ // animation, so only set the alpha and scale for the bubble bar if we didn't
+ // already fully animate in.
+ handle.alpha = 0f
+ bubbleBarView.translationY = ty - offset
+ if (bubbleBarView.alpha != 1f) {
+ // map the path [stashedHandleTranslationY, totalTranslationY] to [0, 1]
+ val fraction =
+ (ty - stashedHandleTranslationY) /
+ (totalTranslationY - stashedHandleTranslationY)
+ bubbleBarView.alpha = fraction
+ bubbleBarView.scaleY =
+ BUBBLE_ANIMATION_INITIAL_SCALE_Y +
+ (1 - BUBBLE_ANIMATION_INITIAL_SCALE_Y) * fraction
+ if (bubbleBarView.alpha > MIN_ALPHA_FOR_TOUCHABLE) {
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
+ }
+ }
+ else -> {
+ // we're past the target animated value, set the alpha and scale for the bubble
+ // bar so that it's fully visible and no longer changing, but keep moving it
+ // along the animation path
+ bubbleBarView.alpha = 1f
+ bubbleBarView.scaleY = 1f
+ bubbleBarView.translationY = ty - offset
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
+ }
+ }
+ animator.addEndListener { _, _, _, canceled, _, _, _ ->
+ // if the show animation was canceled, also cancel the hide animation. this is typically
+ // canceled in this class, but could potentially be canceled elsewhere.
+ if (canceled) {
+ val hideAnimation = animatingBubble?.hideAnimation ?: return@addEndListener
+ scheduler.cancel(hideAnimation)
+ animatingBubble = null
+ bubbleBarView.onAnimatingBubbleCompleted()
+ bubbleBarView.relativePivotY = 1f
+ return@addEndListener
+ }
+ // the bubble bar is now fully settled in. update taskbar touch region so it's touchable
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
+ animator.start()
+ }
+
+ /**
+ * Returns a [Runnable] that starts the animation that hides the bubble bar and morphs it into
+ * the stashed handle.
+ *
+ * Similarly to the show animation, this is visually divided into 2 parts. We first animate the
+ * bubble bar out, and then animate the stash handle in. At the end of the animation we reset
+ * values of the bubble bar.
+ *
+ * This is a spring animation that goes along the same path of the show animation in the
+ * opposite order, and is split into 3 parts:
+ * 1. In the first part the bubble animates out.
+ * 2. In the second part the bubble bar is fully hidden and the handle animates in.
+ * 3. The third part is the overshoot. The handle is made fully visible.
+ */
+ private fun buildBubbleBarToHandleAnimation() = Runnable {
+ if (animatingBubble == null) return@Runnable
+ val offset = bubbleStashController.diffBetweenHandleAndBarCenters
+ val stashedHandleTranslationY =
+ bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
+ // this is the total distance that both the stashed handle and the bar will be traveling
+ val totalTranslationY = bubbleStashController.bubbleBarTranslationYForTaskbar + offset
+ bubbleStashController.setHandleTranslationY(totalTranslationY)
+ val animator = bubbleStashController.stashedHandlePhysicsAnimator
+ animator.setDefaultSpringConfig(springConfig)
+ animator.spring(DynamicAnimation.TRANSLATION_Y, 0f)
+ animator.addUpdateListener { handle, values ->
+ val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener
+ when {
+ ty <= stashedHandleTranslationY -> {
+ // this is the first leg of the animation. only animate the bubble bar. the
+ // handle is hidden during this part
+ bubbleBarView.translationY = ty - offset
+ // map the path [totalTranslationY, stashedHandleTranslationY] to [0, 1]
+ val fraction =
+ (totalTranslationY - ty) / (totalTranslationY - stashedHandleTranslationY)
+ bubbleBarView.alpha = 1 - fraction
+ bubbleBarView.scaleY = 1 - (1 - BUBBLE_ANIMATION_INITIAL_SCALE_Y) * fraction
+ if (bubbleBarView.alpha > MIN_ALPHA_FOR_TOUCHABLE) {
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
+ }
+ ty <= 0 -> {
+ // this is the second part of the animation. make the bubble bar invisible and
+ // start fading in the handle, but don't update the alpha if it's already fully
+ // visible
+ bubbleBarView.alpha = 0f
+ if (handle.alpha != 1f) {
+ // map the path [stashedHandleTranslationY, 0] to [0, 1]
+ val fraction = (stashedHandleTranslationY - ty) / stashedHandleTranslationY
+ handle.alpha = fraction
+ }
+ }
+ else -> {
+ // we reached the target value. set the alpha of the handle to 1
+ handle.alpha = 1f
+ }
+ }
+ }
+ animator.addEndListener { _, _, _, canceled, _, _, _ ->
+ animatingBubble = null
+ if (!canceled) bubbleStashController.stashBubbleBarImmediate()
+ bubbleBarView.onAnimatingBubbleCompleted()
+ bubbleBarView.relativePivotY = 1f
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
+ animator.start()
+ }
+
+ /** Animates to the initial state of the bubble bar, when there are no previous bubbles. */
+ fun animateToInitialState(b: BubbleBarBubble, isInApp: Boolean, isExpanding: Boolean) {
+ val bubbleView = b.view
+ val animator = PhysicsAnimator.getInstance(bubbleView)
+ if (animator.isRunning()) animator.cancel()
+ // the animation of a new bubble is divided into 2 parts. The first part shows the bubble
+ // and the second part hides it after a delay if we are in an app.
+ val showAnimation = buildBubbleBarBounceAnimation()
+ val hideAnimation =
+ if (isInApp && !isExpanding) {
+ buildBubbleBarToHandleAnimation()
+ } else {
+ // in this case the bubble bar remains visible so not much to do. once we implement
+ // the flyout we'll update this runnable to hide it.
+ Runnable {
+ animatingBubble = null
+ bubbleStashController.showBubbleBarImmediate()
+ bubbleBarView.onAnimatingBubbleCompleted()
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
+ }
+ animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
+ scheduler.post(showAnimation)
+ scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
+ }
+
+ private fun buildBubbleBarBounceAnimation() = Runnable {
+ // prepare the bubble bar for the animation
+ bubbleBarView.onAnimatingBubbleStarted()
+ bubbleBarView.translationY = bubbleBarView.height.toFloat()
+ bubbleBarView.visibility = VISIBLE
+ bubbleBarView.alpha = 1f
+ bubbleBarView.scaleX = 1f
+ bubbleBarView.scaleY = 1f
+
+ val animator = PhysicsAnimator.getInstance(bubbleBarView)
+ animator.setDefaultSpringConfig(springConfig)
+ animator.spring(DynamicAnimation.TRANSLATION_Y, bubbleStashController.bubbleBarTranslationY)
+ animator.addUpdateListener { _, _ -> bubbleStashController.updateTaskbarTouchRegion() }
+ animator.addEndListener { _, _, _, _, _, _, _ ->
+ // the bubble bar is now fully settled in. update taskbar touch region so it's touchable
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
+ animator.start()
+ }
+
+ /** Handles touching the animating bubble bar. */
+ fun onBubbleBarTouchedWhileAnimating() {
+ PhysicsAnimator.getInstance(bubbleBarView).cancelIfRunning()
+ bubbleStashController.stashedHandlePhysicsAnimator.cancelIfRunning()
+ val hideAnimation = animatingBubble?.hideAnimation ?: return
+ scheduler.cancel(hideAnimation)
+ bubbleBarView.onAnimatingBubbleCompleted()
+ bubbleBarView.relativePivotY = 1f
+ animatingBubble = null
+ }
+
+ /** Notifies the animator that the taskbar area was touched during an animation. */
+ fun onStashStateChangingWhileAnimating() {
+ val hideAnimation = animatingBubble?.hideAnimation ?: return
+ scheduler.cancel(hideAnimation)
+ animatingBubble = null
+ bubbleStashController.stashedHandlePhysicsAnimator.cancel()
+ bubbleBarView.onAnimatingBubbleCompleted()
+ bubbleBarView.relativePivotY = 1f
+ bubbleStashController.onNewBubbleAnimationInterrupted(
+ /* isStashed= */ bubbleBarView.alpha == 0f,
+ bubbleBarView.translationY
+ )
+ }
+
+ private fun <T> PhysicsAnimator<T>.cancelIfRunning() {
+ if (isRunning()) cancel()
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
new file mode 100644
index 0000000..3c4b63a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.customization
+
+/** Enums for all feature container that taskbar supports. */
+enum class TaskbarContainer {
+ ALL_APPS,
+ DIVIDER,
+ APP_ICONS,
+ RECENTS,
+ NAV_BUTTONS,
+ BUBBLES,
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
new file mode 100644
index 0000000..1ec075f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.customization
+
+import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarControllers
+import com.android.launcher3.taskbar.TaskbarRecentAppsController
+import com.android.launcher3.util.DisplayController
+
+/** Evaluates all the features taskbar can have. */
+class TaskbarFeatureEvaluator(
+ private val taskbarActivityContext: TaskbarActivityContext,
+ private val taskbarControllers: TaskbarControllers,
+) {
+
+ val hasAllApps = true
+ val hasAppIcons = true
+ val hasBubbles = false
+ val hasNavButtons = taskbarActivityContext.isThreeButtonNav
+
+ val hasRecents: Boolean
+ get() = taskbarControllers.taskbarRecentAppsController.isEnabled
+
+ val hasDivider: Boolean
+ get() = enableTaskbarPinning() || hasRecents
+
+ val isTransient: Boolean
+ get() = DisplayController.isTransientTaskbar(taskbarActivityContext)
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
new file mode 100644
index 0000000..4cd895d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.customization
+
+/** Taskbar Icon Specs */
+object TaskbarIconSpecs {
+
+ val iconSize40dp = TaskbarIconSize(40)
+ val iconSize44dp = TaskbarIconSize(44)
+ val iconSize48dp = TaskbarIconSize(48)
+ val iconSize52dp = TaskbarIconSize(52)
+
+ val transientTaskbarIconSizes = arrayOf(iconSize44dp, iconSize48dp, iconSize52dp)
+
+ val defaultPersistentIconSize = iconSize40dp
+ val defaultTransientIconSize = iconSize44dp
+
+ // defined as row, columns
+ val transientTaskbarIconSizeByGridSize =
+ mapOf(
+ Pair(6, 5) to iconSize52dp,
+ Pair(4, 5) to iconSize48dp,
+ Pair(5, 4) to iconSize48dp,
+ Pair(4, 4) to iconSize48dp,
+ Pair(5, 6) to iconSize44dp,
+ )
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
new file mode 100644
index 0000000..02e5947
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.customization
+
+/** Evaluates the taskbar specs based on the taskbar grid size and the taskbar icon size. */
+class TaskbarSpecsEvaluator(private val taskbarFeatureEvaluator: TaskbarFeatureEvaluator) {
+
+ fun getIconSizeByGrid(row: Int, column: Int): TaskbarIconSize {
+ return if (taskbarFeatureEvaluator.isTransient) {
+ TaskbarIconSpecs.transientTaskbarIconSizeByGridSize.getOrDefault(
+ Pair(row, column),
+ TaskbarIconSpecs.defaultTransientIconSize,
+ )
+ } else {
+ TaskbarIconSpecs.defaultPersistentIconSize
+ }
+ }
+
+ fun getIconSizeStepDown(iconSize: TaskbarIconSize): TaskbarIconSize {
+ if (!taskbarFeatureEvaluator.isTransient) return TaskbarIconSpecs.defaultPersistentIconSize
+
+ val currentIconSizeIndex = TaskbarIconSpecs.transientTaskbarIconSizes.indexOf(iconSize)
+ // return the current icon size if supplied icon size is unknown or we have reached the
+ // min icon size.
+ return if (currentIconSizeIndex == -1 || currentIconSizeIndex == 0) iconSize
+ else TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex - 1]
+ }
+
+ fun getIconSizeStepUp(iconSize: TaskbarIconSize): TaskbarIconSize {
+ if (!taskbarFeatureEvaluator.isTransient) return TaskbarIconSpecs.defaultPersistentIconSize
+
+ val currentIconSizeIndex = TaskbarIconSpecs.transientTaskbarIconSizes.indexOf(iconSize)
+ // return the current icon size if supplied icon size is unknown or we have reached the
+ // max icon size.
+ return if (
+ currentIconSizeIndex == -1 ||
+ currentIconSizeIndex == TaskbarIconSpecs.transientTaskbarIconSizes.size - 1
+ ) {
+ iconSize
+ } else {
+ TaskbarIconSpecs.transientTaskbarIconSizes.get(currentIconSizeIndex + 1)
+ }
+ }
+}
+
+data class TaskbarIconSize(val size: Int)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index fe91362..e487f9f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -24,10 +24,11 @@
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
+import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.Utilities
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
-import com.android.systemui.shared.rotation.RotationButton
/**
* Meant to be a simple container for data subclasses will need
@@ -41,14 +42,13 @@
* @property startContextualContainer ViewGroup that holds the start contextual button (ex, A11y).
*/
abstract class AbstractNavButtonLayoutter(
- val resources: Resources,
- val navButtonContainer: LinearLayout,
- protected val endContextualContainer: ViewGroup,
- protected val startContextualContainer: ViewGroup,
- protected val imeSwitcher: ImageView?,
- protected val rotationButton: RotationButton?,
- protected val a11yButton: ImageView?,
- protected val space: Space?
+ val resources: Resources,
+ val navButtonContainer: LinearLayout,
+ protected val endContextualContainer: ViewGroup,
+ protected val startContextualContainer: ViewGroup,
+ protected val imeSwitcher: ImageView?,
+ protected val a11yButton: ImageView?,
+ protected val space: Space?
) : NavButtonLayoutter {
protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
@@ -66,17 +66,41 @@
}
fun getParamsToCenterView(): FrameLayout.LayoutParams {
- val params = FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ val params =
+ FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
params.gravity = Gravity.CENTER
- return params;
+ return params
}
- open fun repositionContextualContainer(contextualContainer: ViewGroup, buttonSize: Int,
- barAxisMarginStart: Int, barAxisMarginEnd: Int,
- gravity: Int) {
- val contextualContainerParams = FrameLayout.LayoutParams(
- buttonSize, ViewGroup.LayoutParams.MATCH_PARENT)
+ fun adjustForSetupInPhoneMode(
+ navButtonsLayoutParams: FrameLayout.LayoutParams,
+ navButtonsViewLayoutParams: FrameLayout.LayoutParams,
+ deviceProfile: DeviceProfile
+ ) {
+ val phoneOrPortraitSetupMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_margin)
+ navButtonsLayoutParams.marginStart = phoneOrPortraitSetupMargin
+ navButtonsLayoutParams.bottomMargin =
+ if (!deviceProfile.isLandscape) 0
+ else
+ phoneOrPortraitSetupMargin -
+ resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2
+ navButtonsViewLayoutParams.height =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_height)
+ }
+
+ open fun repositionContextualContainer(
+ contextualContainer: ViewGroup,
+ buttonSize: Int,
+ barAxisMarginStart: Int,
+ barAxisMarginEnd: Int,
+ gravity: Int
+ ) {
+ val contextualContainerParams =
+ FrameLayout.LayoutParams(buttonSize, ViewGroup.LayoutParams.MATCH_PARENT)
contextualContainerParams.apply {
marginStart = barAxisMarginStart
marginEnd = barAxisMarginEnd
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index 4368b95..aa8f5a3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -29,27 +29,24 @@
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
-import com.android.systemui.shared.rotation.RotationButton
class KidsNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
@@ -105,11 +102,16 @@
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
- val contextualMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_contextual_button_padding)
+ val contextualMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
- repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
- contextualMargin, Gravity.START)
+ repositionContextualContainer(
+ startContextualContainer,
+ WRAP_CONTENT,
+ contextualMargin,
+ contextualMargin,
+ Gravity.START
+ )
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
@@ -119,9 +121,5 @@
endContextualContainer.addView(a11yButton)
a11yButton.layoutParams = getParamsToCenterView()
}
- if (rotationButton != null) {
- endContextualContainer.addView(rotationButton.currentView)
- rotationButton.currentView.layoutParams = getParamsToCenterView()
- }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 2b60dc0..2497fbb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -20,16 +20,16 @@
import android.view.Surface.ROTATION_90
import android.view.Surface.Rotation
import android.view.ViewGroup
-import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
import com.android.launcher3.DeviceProfile
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_CONTEXTUAL_BUTTONS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_NAV_BUTTONS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_START_CONTEXTUAL_BUTTONS
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.Companion
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
-import com.android.systemui.shared.rotation.RotationButton
/**
* Select the correct layout for nav buttons
@@ -56,18 +56,17 @@
* @param isThreeButtonNav are no-ops when taskbar is present/showing
*/
fun getUiLayoutter(
- deviceProfile: DeviceProfile,
- navButtonsView: NearestTouchFrame,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?,
- resources: Resources,
- isKidsMode: Boolean,
- isInSetup: Boolean,
- isThreeButtonNav: Boolean,
- phoneMode: Boolean,
- @Rotation surfaceRotation: Int
+ deviceProfile: DeviceProfile,
+ navButtonsView: NearestTouchFrame,
+ imeSwitcher: ImageView?,
+ a11yButton: ImageView?,
+ space: Space?,
+ resources: Resources,
+ isKidsMode: Boolean,
+ isInSetup: Boolean,
+ isThreeButtonNav: Boolean,
+ phoneMode: Boolean,
+ @Rotation surfaceRotation: Int
): NavButtonLayoutter {
val navButtonContainer =
navButtonsView.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS)
@@ -82,89 +81,84 @@
if (!deviceProfile.isLandscape) {
navButtonsView.setIsVertical(false)
PhonePortraitNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
- )
- } else if (surfaceRotation == ROTATION_90) {
- navButtonsView.setIsVertical(true)
- PhoneLandscapeNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
- )
- } else {
- navButtonsView.setIsVertical(true)
- PhoneSeascapeNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
- )
- }
- }
- isPhoneGestureMode ->{
- PhoneGestureLayoutter(
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
- rotationButton,
a11yButton,
space
+ )
+ } else if (surfaceRotation == ROTATION_90) {
+ navButtonsView.setIsVertical(true)
+ PhoneLandscapeNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
+ )
+ } else {
+ navButtonsView.setIsVertical(true)
+ PhoneSeascapeNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
+ )
+ }
+ }
+ isPhoneGestureMode -> {
+ PhoneGestureLayoutter(
+ resources,
+ navButtonsView,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
)
}
deviceProfile.isTaskbarPresent -> {
return when {
isInSetup -> {
SetupNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navButtonsView,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
)
}
isKidsMode -> {
KidsNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
)
}
else ->
TaskbarNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
index bf820c0..390ec34 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -17,36 +17,60 @@
package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
+import android.view.Gravity
import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
+import com.android.launcher3.DeviceProfile
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.systemui.shared.rotation.RotationButton
/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
class PhoneGestureLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?
+ resources: Resources,
+ navButtonsView: NearestTouchFrame,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ a11yButton: ImageView?,
+ space: Space?
) :
- AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
- ) {
+ AbstractNavButtonLayoutter(
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
+ ) {
+ private val mNavButtonsView = navButtonsView
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
+ // TODO: look into if we should use SetupNavLayoutter instead.
+ if (!context.isUserSetupComplete) {
+ // Since setup wizard only has back button enabled, it looks strange to be
+ // end-aligned, so start-align instead.
+ val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
+ val navButtonsViewLayoutParams =
+ mNavButtonsView.layoutParams as FrameLayout.LayoutParams
+ val deviceProfile: DeviceProfile = context.deviceProfile
+
+ navButtonsLayoutParams.marginEnd = 0
+ navButtonsLayoutParams.gravity = Gravity.START
+ context.setTaskbarWindowSize(context.setupWindowSize)
+
+ adjustForSetupInPhoneMode(
+ navButtonsLayoutParams,
+ navButtonsViewLayoutParams,
+ deviceProfile
+ )
+ mNavButtonsView.layoutParams = navButtonsViewLayoutParams
+ navButtonContainer.layoutParams = navButtonsLayoutParams
+ }
+
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 6a935f1..9f7f07e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -26,35 +26,32 @@
import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.systemui.shared.rotation.RotationButton
open class PhoneLandscapeNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
val totalHeight = context.deviceProfile.heightPx
- val homeButtonHeight = resources.getDimensionPixelSize(
- R.dimen.taskbar_phone_home_button_size)
- val roundedCornerContentMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_phone_rounded_corner_content_margin)
+ val homeButtonHeight =
+ resources.getDimensionPixelSize(R.dimen.taskbar_phone_home_button_size)
+ val roundedCornerContentMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_phone_rounded_corner_content_margin)
val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
val contentWidth = totalHeight - roundedCornerContentMargin * 2 - contentPadding * 2
@@ -63,13 +60,13 @@
val sideButtonHeight = contextualButtonHeight * 2
val navButtonContainerHeight = contentWidth - contextualButtonHeight * 2
- val navContainerParams = FrameLayout.LayoutParams(
- MATCH_PARENT, navButtonContainerHeight.toInt())
+ val navContainerParams =
+ FrameLayout.LayoutParams(MATCH_PARENT, navButtonContainerHeight.toInt())
navContainerParams.apply {
topMargin =
- (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
+ (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
bottomMargin =
- (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
+ (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
marginEnd = 0
marginStart = 0
}
@@ -84,8 +81,8 @@
navButtonContainer.gravity = Gravity.CENTER
// Add the spaces in between the nav buttons
- val spaceInBetween = (navButtonContainerHeight - homeButtonHeight -
- sideButtonHeight * 2) / 2.0f
+ val spaceInBetween =
+ (navButtonContainerHeight - homeButtonHeight - sideButtonHeight * 2) / 2.0f
for (i in 0 until navButtonContainer.childCount) {
val navButton = navButtonContainer.getChildAt(i)
val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
@@ -124,13 +121,23 @@
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
- val roundedCornerContentMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_phone_rounded_corner_content_margin)
+ val roundedCornerContentMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_phone_rounded_corner_content_margin)
val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
- repositionContextualContainer(startContextualContainer, buttonSize,
- roundedCornerContentMargin + contentPadding, 0, Gravity.TOP)
- repositionContextualContainer(endContextualContainer, buttonSize,
- 0, roundedCornerContentMargin + contentPadding, Gravity.BOTTOM)
+ repositionContextualContainer(
+ startContextualContainer,
+ buttonSize,
+ roundedCornerContentMargin + contentPadding,
+ 0,
+ Gravity.TOP
+ )
+ repositionContextualContainer(
+ endContextualContainer,
+ buttonSize,
+ 0,
+ roundedCornerContentMargin + contentPadding,
+ Gravity.BOTTOM
+ )
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
@@ -140,16 +147,16 @@
startContextualContainer.addView(a11yButton)
a11yButton.layoutParams = getParamsToCenterView()
}
- if (rotationButton != null) {
- startContextualContainer.addView(rotationButton.currentView)
- rotationButton.currentView.layoutParams = getParamsToCenterView()
- }
endContextualContainer.addView(space, MATCH_PARENT, MATCH_PARENT)
}
- override fun repositionContextualContainer(contextualContainer: ViewGroup, buttonSize: Int,
- barAxisMarginTop: Int, barAxisMarginBottom: Int,
- gravity: Int) {
+ override fun repositionContextualContainer(
+ contextualContainer: ViewGroup,
+ buttonSize: Int,
+ barAxisMarginTop: Int,
+ barAxisMarginBottom: Int,
+ gravity: Int
+ ) {
val contextualContainerParams = FrameLayout.LayoutParams(MATCH_PARENT, buttonSize)
contextualContainerParams.apply {
marginStart = 0
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index 0672270..5b24ebf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -26,34 +26,32 @@
import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.systemui.shared.rotation.RotationButton
class PhonePortraitNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
val totalWidth = context.deviceProfile.widthPx
- val homeButtonWidth = resources.getDimensionPixelSize(R.dimen.taskbar_phone_home_button_size)
- val roundedCornerContentMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_phone_rounded_corner_content_margin)
+ val homeButtonWidth =
+ resources.getDimensionPixelSize(R.dimen.taskbar_phone_home_button_size)
+ val roundedCornerContentMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_phone_rounded_corner_content_margin)
val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
val contentWidth = totalWidth - roundedCornerContentMargin * 2 - contentPadding * 2
@@ -62,15 +60,18 @@
val sideButtonWidth = contextualButtonWidth * 2
val navButtonContainerWidth = contentWidth - contextualButtonWidth * 2
- val navContainerParams = FrameLayout.LayoutParams(navButtonContainerWidth.toInt(),
- ViewGroup.LayoutParams.MATCH_PARENT)
+ val navContainerParams =
+ FrameLayout.LayoutParams(
+ navButtonContainerWidth.toInt(),
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
navContainerParams.apply {
topMargin = 0
bottomMargin = 0
marginEnd =
- (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+ (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
marginStart =
- (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+ (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
}
// Ensure order of buttons is correct
@@ -85,8 +86,8 @@
navButtonContainer.gravity = Gravity.CENTER
// Add the spaces in between the nav buttons
- val spaceInBetween = (navButtonContainerWidth - homeButtonWidth -
- sideButtonWidth * 2) / 2.0f
+ val spaceInBetween =
+ (navButtonContainerWidth - homeButtonWidth - sideButtonWidth * 2) / 2.0f
for (i in 0 until navButtonContainer.childCount) {
val navButton = navButtonContainer.getChildAt(i)
val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
@@ -114,10 +115,20 @@
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
- repositionContextualContainer(startContextualContainer, contextualButtonWidth.toInt(),
- roundedCornerContentMargin + contentPadding, 0, Gravity.START)
- repositionContextualContainer(endContextualContainer, contextualButtonWidth.toInt(), 0,
- roundedCornerContentMargin + contentPadding, Gravity.END)
+ repositionContextualContainer(
+ startContextualContainer,
+ contextualButtonWidth.toInt(),
+ roundedCornerContentMargin + contentPadding,
+ 0,
+ Gravity.START
+ )
+ repositionContextualContainer(
+ endContextualContainer,
+ contextualButtonWidth.toInt(),
+ 0,
+ roundedCornerContentMargin + contentPadding,
+ Gravity.END
+ )
startContextualContainer.addView(space, MATCH_PARENT, MATCH_PARENT)
if (imeSwitcher != null) {
@@ -128,9 +139,5 @@
endContextualContainer.addView(a11yButton)
a11yButton.layoutParams = getParamsToCenterView()
}
- if (rotationButton != null) {
- endContextualContainer.addView(rotationButton.currentView)
- rotationButton.currentView.layoutParams = getParamsToCenterView()
- }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
index 869cc43..f0b47f4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -1,18 +1,18 @@
/*
-* Copyright (C) 2023 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License
-*/
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
package com.android.launcher3.taskbar.navbutton
@@ -24,28 +24,25 @@
import android.widget.LinearLayout
import android.widget.Space
import com.android.launcher3.R
-import com.android.systemui.shared.rotation.RotationButton
class PhoneSeascapeNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ a11yButton: ImageView?,
+ space: Space?
) :
- PhoneLandscapeNavLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
- ) {
+ PhoneLandscapeNavLayoutter(
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
+ ) {
override fun addThreeButtons() {
// Flip ordering of back and recents buttons
@@ -58,13 +55,23 @@
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
- val roundedCornerContentMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_phone_rounded_corner_content_margin)
+ val roundedCornerContentMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_phone_rounded_corner_content_margin)
val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
- repositionContextualContainer(startContextualContainer, buttonSize,
- roundedCornerContentMargin + contentPadding, 0, Gravity.TOP)
- repositionContextualContainer(endContextualContainer, buttonSize, 0,
- roundedCornerContentMargin + contentPadding, Gravity.BOTTOM)
+ repositionContextualContainer(
+ startContextualContainer,
+ buttonSize,
+ roundedCornerContentMargin + contentPadding,
+ 0,
+ Gravity.TOP
+ )
+ repositionContextualContainer(
+ endContextualContainer,
+ buttonSize,
+ 0,
+ roundedCornerContentMargin + contentPadding,
+ Gravity.BOTTOM
+ )
startContextualContainer.addView(space, MATCH_PARENT, MATCH_PARENT)
if (imeSwitcher != null) {
@@ -75,9 +82,5 @@
endContextualContainer.addView(a11yButton)
a11yButton.layoutParams = getParamsToCenterView()
}
- if (rotationButton != null) {
- endContextualContainer.addView(rotationButton.currentView)
- rotationButton.currentView.layoutParams = getParamsToCenterView()
- }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index 181e0ed..22a3630 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -24,50 +24,81 @@
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
+import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.systemui.shared.rotation.RotationButton
+
+const val SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95
+const val SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05
class SetupNavLayoutter(
- resources: Resources,
- navButtonContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?
+ resources: Resources,
+ navButtonsView: NearestTouchFrame,
+ navButtonContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
) {
+ private val mNavButtonsView = navButtonsView
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
// Since setup wizard only has back button enabled, it looks strange to be
// end-aligned, so start-align instead.
val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
- navButtonsLayoutParams.apply {
- marginStart = navButtonsLayoutParams.marginEnd
- marginEnd = 0
- gravity = Gravity.START
+ val navButtonsViewLayoutParams = mNavButtonsView.layoutParams as FrameLayout.LayoutParams
+ val deviceProfile: DeviceProfile = context.deviceProfile
+
+ navButtonsLayoutParams.marginEnd = 0
+ navButtonsLayoutParams.gravity = Gravity.START
+ context.setTaskbarWindowSize(context.setupWindowSize)
+
+ // If SUW is on a large screen device that is landscape (or has a square aspect
+ // ratio) the back button has to be placed accordingly
+ if (
+ deviceProfile.isTablet && deviceProfile.isLandscape ||
+ (deviceProfile.aspectRatio > SQUARE_ASPECT_RATIO_BOTTOM_BOUND &&
+ deviceProfile.aspectRatio < SQUARE_ASPECT_RATIO_UPPER_BOUND)
+ ) {
+ navButtonsLayoutParams.marginStart =
+ resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin)
+ navButtonsViewLayoutParams.bottomMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_bottom_margin)
+ navButtonsLayoutParams.height =
+ resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_height)
+ } else {
+ adjustForSetupInPhoneMode(
+ navButtonsLayoutParams,
+ navButtonsViewLayoutParams,
+ deviceProfile
+ )
}
- navButtonContainer.requestLayout()
+ mNavButtonsView.layoutParams = navButtonsViewLayoutParams
+ navButtonContainer.layoutParams = navButtonsLayoutParams
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
- val contextualMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_contextual_button_padding)
+ val contextualMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
- repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
- contextualMargin, Gravity.START)
+ repositionContextualContainer(
+ startContextualContainer,
+ WRAP_CONTENT,
+ contextualMargin,
+ contextualMargin,
+ Gravity.START
+ )
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
@@ -77,9 +108,5 @@
endContextualContainer.addView(a11yButton)
a11yButton.layoutParams = getParamsToCenterView()
}
- if (rotationButton != null) {
- endContextualContainer.addView(rotationButton.currentView)
- rotationButton.currentView.layoutParams = getParamsToCenterView()
- }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 5c57a01..a59e8a8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -26,37 +26,38 @@
import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.systemui.shared.rotation.RotationButton
-/**
- * Layoutter for rendering task bar in large screen, both in 3-button and gesture nav mode.
- */
+/** Layoutter for rendering task bar in large screen, both in 3-button and gesture nav mode. */
class TaskbarNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
// Add spacing after the end of the last nav button
- var navMarginEnd = resources
- .getDimension(context.deviceProfile.inv.inlineNavButtonsEndSpacing)
- .toInt()
+ var navMarginEnd =
+ resources.getDimension(context.deviceProfile.inv.inlineNavButtonsEndSpacing).toInt()
+
+ val cutout = context.display.cutout
+ val bottomRect = cutout?.boundingRectBottom
+ if (bottomRect != null && !bottomRect.isEmpty) {
+ navMarginEnd = bottomRect.width()
+ }
+
val contextualWidth = endContextualContainer.width
// If contextual buttons are showing, we check if the end margin is enough for the
// contextual button to be showing - if not, move the nav buttons over a smidge
@@ -65,8 +66,11 @@
navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
}
- val navButtonParams = FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ val navButtonParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
navButtonParams.apply {
gravity = Gravity.END or Gravity.CENTER_VERTICAL
marginEnd = navMarginEnd
@@ -98,24 +102,38 @@
startContextualContainer.removeAllViews()
if (!context.deviceProfile.isGestureMode) {
- val contextualMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_contextual_button_padding)
+ val contextualMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
- repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
- contextualMargin, Gravity.START)
+ repositionContextualContainer(
+ startContextualContainer,
+ WRAP_CONTENT,
+ contextualMargin,
+ contextualMargin,
+ Gravity.START
+ )
if (imeSwitcher != null) {
+ val imeStartMargin =
+ resources.getDimensionPixelSize(
+ R.dimen.taskbar_ime_switcher_button_margin_start
+ )
startContextualContainer.addView(imeSwitcher)
- imeSwitcher.layoutParams = getParamsToCenterView()
+ val imeSwitcherButtonParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ imeSwitcherButtonParams.apply {
+ marginStart = imeStartMargin
+ gravity = Gravity.CENTER_VERTICAL
+ }
+ imeSwitcher.layoutParams = imeSwitcherButtonParams
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
a11yButton.layoutParams = getParamsToCenterView()
}
- if (rotationButton != null) {
- endContextualContainer.addView(rotationButton.currentView)
- rotationButton.currentView.layoutParams = getParamsToCenterView()
- }
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
index adbec65..7eb34a5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
@@ -133,16 +133,19 @@
* <p>
* This method should be called after an exit animation finishes, if applicable.
*/
- @SuppressLint("WrongConstant")
void maybeCloseWindow() {
- if (mOverlayContext != null && (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)
- || mOverlayContext.getDragController().isSystemDragInProgress())) {
- return;
- }
+ if (!canCloseWindow()) return;
mProxyView.close(false);
onDestroy();
}
+ @SuppressLint("WrongConstant")
+ private boolean canCloseWindow() {
+ if (mOverlayContext == null) return true;
+ if (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)) return false;
+ return !mOverlayContext.getDragController().isSystemDragInProgress();
+ }
+
/** Destroys the controller and any overlay window if present. */
public void onDestroy() {
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
@@ -212,10 +215,17 @@
@Override
protected void handleClose(boolean animate) {
- if (mIsOpen) {
- mTaskbarContext.getDragLayer().removeView(this);
- Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate));
- }
+ if (!mIsOpen) return;
+ mTaskbarContext.getDragLayer().removeView(this);
+ Optional.ofNullable(mOverlayContext).ifPresent(c -> {
+ if (canCloseWindow()) {
+ onDestroy(); // Window is already ready to be destroyed.
+ } else {
+ // Close window's AFVs before destroying it. Its drag layer will attempt to
+ // close the proxy view again once its children are removed.
+ closeAllOpenViews(c, animate);
+ }
+ });
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
index 9c3e8af..773b0b9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
@@ -30,7 +30,7 @@
import androidx.annotation.NonNull;
-import com.android.app.viewcapture.SettingsAwareViewCapture;
+import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -59,7 +59,7 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- mViewCaptureCloseable = SettingsAwareViewCapture.getInstance(getContext())
+ mViewCaptureCloseable = ViewCaptureFactory.getInstance(getContext())
.startCapture(getRootView(), ".TaskbarOverlay");
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
deleted file mode 100644
index dcc3b05..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherUserInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.ArrayMap;
-import android.window.RemoteTransition;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.proxy.ProxyActivityStarter;
-import com.android.launcher3.util.StartActivityParams;
-import com.android.launcher3.util.UserIconInfo;
-import com.android.quickstep.util.FadeOutRemoteTransition;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A wrapper for the hidden API calls
- */
-public class ApiWrapper {
-
- public static final boolean TASKBAR_DRAWN_IN_PROCESS = true;
-
- public static Person[] getPersons(ShortcutInfo si) {
- Person[] persons = si.getPersons();
- return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
- }
-
- public static Map<String, LauncherActivityInfo> getActivityOverrides(Context context) {
- return context.getSystemService(LauncherApps.class).getActivityOverrides();
- }
-
- /**
- * Creates an ActivityOptions to play fade-out animation on closing targets
- */
- public static ActivityOptions createFadeOutAnimOptions(Context context) {
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setRemoteTransition(new RemoteTransition(new FadeOutRemoteTransition()));
- return options;
- }
-
- /**
- * Returns a map of all users on the device to their corresponding UI properties
- */
- public static Map<UserHandle, UserIconInfo> queryAllUsers(Context context) {
- UserManager um = context.getSystemService(UserManager.class);
- Map<UserHandle, UserIconInfo> users = new ArrayMap<>();
- List<UserHandle> usersActual = um.getUserProfiles();
- if (usersActual != null) {
- for (UserHandle user : usersActual) {
- if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()) {
- LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
- LauncherUserInfo launcherUserInfo = launcherApps.getLauncherUserInfo(user);
- if (launcherUserInfo == null) {
- continue;
- }
- // UserTypes not supported in Launcher are deemed to be the current
- // Foreground User.
- int userType = switch (launcherUserInfo.getUserType()) {
- case UserManager.USER_TYPE_PROFILE_MANAGED -> UserIconInfo.TYPE_WORK;
- case UserManager.USER_TYPE_PROFILE_CLONE -> UserIconInfo.TYPE_CLONED;
- case UserManager.USER_TYPE_PROFILE_PRIVATE -> UserIconInfo.TYPE_PRIVATE;
- default -> UserIconInfo.TYPE_MAIN;
- };
- long serial = launcherUserInfo.getUserSerialNumber();
- users.put(user, new UserIconInfo(user, userType, serial));
- } else {
- long serial = um.getSerialNumberForUser(user);
-
- // Simple check to check if the provided user is work profile
- // TODO: Migrate to a better platform API
- NoopDrawable d = new NoopDrawable();
- boolean isWork = (d != context.getPackageManager().getUserBadgedIcon(d, user));
- UserIconInfo info = new UserIconInfo(
- user,
- isWork ? UserIconInfo.TYPE_WORK : UserIconInfo.TYPE_MAIN,
- serial);
- users.put(user, info);
- }
- }
- }
- return users;
- }
-
- /**
- * Returns the list of the system packages that are installed at user creation.
- * An empty list denotes that all system packages are installed for that user at creation.
- */
- public static List<String> getPreInstalledSystemPackages(Context context, UserHandle user) {
- LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
- if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()
- && Flags.privateSpaceSysAppsSeparation()) {
- return launcherApps.getPreInstalledSystemPackages(user);
- } else {
- return new ArrayList<>();
- }
- }
-
- /**
- * Returns an intent which can be used to start the App Market activity (Installer
- * Activity).
- */
- public static Intent getAppMarketActivityIntent(Context context, String packageName,
- UserHandle user) {
- LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
- if (android.os.Flags.allowPrivateProfile()
- && Flags.enablePrivateSpace()
- && (Flags.privateSpaceAppInstallerButton()
- || Flags.enablePrivateSpaceInstallShortcut())) {
- StartActivityParams params = new StartActivityParams((PendingIntent) null, 0);
- params.intentSender = launcherApps.getAppMarketActivityIntent(packageName, user);
- ActivityOptions options = ActivityOptions.makeBasic()
- .setPendingIntentBackgroundActivityStartMode(ActivityOptions
- .MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- params.options = options.toBundle();
- params.requireActivityResult = false;
- return ProxyActivityStarter.getLaunchIntent(context, params);
- } else {
- return new Intent(Intent.ACTION_VIEW)
- .setData(new Uri.Builder()
- .scheme("market")
- .authority("details")
- .appendQueryParameter("id", packageName)
- .build())
- .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
- .authority(context.getPackageName()).build());
- }
- }
-
- /**
- * Checks if an activity is flagged as non-resizeable.
- */
- public static boolean isNonResizeableActivity(LauncherActivityInfo lai) {
- return lai.getActivityInfo().resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
- }
-
- private static class NoopDrawable extends ColorDrawable {
- @Override
- public int getIntrinsicHeight() {
- return 1;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return 1;
- }
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 2eced74..14d391b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -71,7 +71,7 @@
ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, scaleAndOffset[1]);
TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
- getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
+ getContentAlphaProperty().set(mRecentsView, state.isRecentsViewVisible ? 1f : 0);
getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
RECENTS_GRID_PROGRESS.set(mRecentsView,
state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
@@ -109,7 +109,8 @@
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
- boolean exitingOverview = !FeatureFlags.enableSplitContextually() && !toState.overviewUi;
+ boolean exitingOverview =
+ !FeatureFlags.enableSplitContextually() && !toState.isRecentsViewVisible;
if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
.createPlaceholderDismissAnim(mLauncher, LAUNCHER_SPLIT_SELECTION_EXIT_HOME,
@@ -124,7 +125,8 @@
);
}
- setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
+ setter.setFloat(mRecentsView, getContentAlphaProperty(),
+ toState.isRecentsViewVisible ? 1 : 0,
config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
setter.setFloat(
@@ -145,7 +147,7 @@
private Interpolator getOverviewInterpolator(LauncherState fromState, LauncherState toState) {
return fromState == QUICK_SWITCH_FROM_HOME
? ACCELERATE_DECELERATE
- : toState.overviewUi ? INSTANT : FINAL_FRAME;
+ : toState.isRecentsViewVisible ? INSTANT : FINAL_FRAME;
}
abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
deleted file mode 100644
index 8f1d319..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.launcher3.R;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-
-/** A util class that inflates a predicted app icon */
-public class PredictedAppIconInflater {
- public static View inflate(LayoutInflater inflater, ViewGroup parent, WorkspaceItemInfo info) {
- PredictedAppIcon icon = (PredictedAppIcon) inflater.inflate(
- R.layout.predicted_app_icon, parent, false);
- icon.applyFromWorkspaceItem(info);
- return icon;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 2c45129..2168f7a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -21,6 +21,7 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
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.enablePredictiveBackGesture;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
@@ -39,8 +40,8 @@
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
@@ -61,8 +62,9 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
+import static com.android.window.flags.Flags.enableDesktopWindowingMode;
+import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
import android.animation.Animator;
@@ -84,9 +86,9 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.Display;
import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
import android.view.View;
import android.widget.AnalogClock;
import android.widget.TextClock;
@@ -101,11 +103,11 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import com.android.app.viewcapture.SettingsAwareViewCapture;
+import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
-import com.android.launcher3.HomeTransitionController;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherState;
@@ -164,6 +166,8 @@
import com.android.launcher3.util.TouchController;
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.quickstep.OverviewCommandHelper;
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
@@ -181,6 +185,7 @@
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -189,9 +194,12 @@
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.unfold.dagger.UnfoldMain;
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
import com.android.systemui.unfold.updates.RotationChangeProvider;
+import kotlin.Unit;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -204,14 +212,11 @@
import java.util.function.Predicate;
import java.util.stream.Stream;
-public class QuickstepLauncher extends Launcher {
+public class QuickstepLauncher extends Launcher implements RecentsViewContainer {
private static final boolean TRACE_LAYOUTS =
SystemProperties.getBoolean("persist.debug.trace_layouts", false);
private static final String TRACE_RELAYOUT_CLASS =
SystemProperties.get("persist.debug.trace_request_layout_class", null);
-
- private static final String TAG = "QuickstepLauncher";
-
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
protected static final String RING_APPEAR_ANIMATION_PREFIX = "RingAppearAnimation\t";
@@ -219,9 +224,10 @@
private FixedContainerItems mAllAppsPredictions;
private HotseatPredictionController mHotseatPredictionController;
private DepthController mDepthController;
- private DesktopVisibilityController mDesktopVisibilityController;
+ private @Nullable DesktopVisibilityController mDesktopVisibilityController;
private QuickstepTransitionManager mAppTransitionManager;
- private OverviewActionsView mActionsView;
+
+ private OverviewActionsView<?> mActionsView;
private TISBindHelper mTISBindHelper;
private @Nullable LauncherTaskbarUIController mTaskbarUIController;
// Will be updated when dragging from taskbar.
@@ -247,21 +253,27 @@
private boolean mIsPredictiveBackToHomeInProgress;
- private HomeTransitionController mHomeTransitionController;
+ public static QuickstepLauncher getLauncher(Context context) {
+ return fromContext(context);
+ }
@Override
protected void setupViews() {
super.setupViews();
mActionsView = findViewById(R.id.overview_actions_view);
- RecentsView overviewPanel = getOverviewPanel();
+ RecentsView<?,?> overviewPanel = getOverviewPanel();
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
mSplitSelectStateController =
new SplitSelectStateController(this, mHandler, getStateManager(),
getDepthController(), getStatsLogManager(),
systemUiProxy, RecentsModel.INSTANCE.get(this),
() -> onStateBack());
- if (isDesktopModeSupported()) {
+ RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(asContext());
+ // TODO(b/337863494): Explore use of the same OverviewComponentObserver across launcher
+ OverviewComponentObserver overviewComponentObserver = new OverviewComponentObserver(
+ asContext(), deviceState);
+ if (enableDesktopWindowingMode()) {
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
getStateManager(), systemUiProxy, getIApplicationThread(),
getDepthController());
@@ -269,7 +281,7 @@
overviewPanel.init(mActionsView, mSplitSelectStateController,
mDesktopRecentsTransitionController);
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
- mSplitSelectStateController);
+ mSplitSelectStateController, overviewComponentObserver, deviceState);
mSplitToWorkspaceController = new SplitToWorkspaceController(this,
mSplitSelectStateController);
mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
@@ -279,17 +291,13 @@
mAppTransitionManager.registerRemoteAnimations();
mAppTransitionManager.registerRemoteTransitions();
- if (FeatureFlags.enableHomeTransitionListener()) {
- mHomeTransitionController = new HomeTransitionController();
- mHomeTransitionController.registerHomeTransitionListener(this);
- }
-
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
mDepthController = new DepthController(this);
- mDesktopVisibilityController = new DesktopVisibilityController(this);
- if (isDesktopModeSupported()) {
+ if (enableDesktopWindowingMode()) {
+ mDesktopVisibilityController = new DesktopVisibilityController(this);
mDesktopVisibilityController.registerSystemUiListener();
- mSplitSelectStateController.initSplitFromDesktopController(this);
+ mSplitSelectStateController.initSplitFromDesktopController(this,
+ overviewComponentObserver);
}
mHotseatPredictionController = new HotseatPredictionController(this);
@@ -375,8 +383,6 @@
public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) {
// Only pause is taskbar controller is not present until the transition (if it exists) ends
mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
- Log.d("b/318394698", "startActivitySafely being run, getTaskbarUIController is: "
- + getTaskbarUIController());
PredictionRowView<?> predictionRowView =
getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class);
// Pause the prediction row updates until the transition (if it exists) ends.
@@ -395,6 +401,12 @@
}
@Override
+ public void startBinding() {
+ super.startBinding();
+ mHotseatPredictionController.verifyUIUpdateNotPaused();
+ }
+
+ @Override
protected void onActivityFlagsChanged(int changeBits) {
if ((changeBits & ACTIVITY_STATE_STARTED) != 0) {
mDepthController.setActivityStarted(isStarted());
@@ -489,7 +501,6 @@
@Override
public void bindExtraContainerItems(FixedContainerItems item) {
- Log.d(TAG, "Bind extra container items. ContainerId = " + item.containerId);
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
PredictionRowView<?> predictionRowView =
@@ -497,7 +508,6 @@
PredictionRowView.class);
predictionRowView.setPredictedApps(item.items);
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
- Log.d(TAG, "Bind extra container item is hotseat prediction");
mHotseatPredictionController.setPredictedItems(item);
} else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
getPopupDataProvider().setRecommendedWidgets(item.items);
@@ -529,10 +539,6 @@
mLauncherUnfoldAnimationController.onDestroy();
}
- if (mHomeTransitionController != null) {
- mHomeTransitionController.unregisterHomeTransitionListener();
- }
-
if (mDesktopVisibilityController != null) {
mDesktopVisibilityController.unregisterSystemUiListener();
}
@@ -583,6 +589,7 @@
} else {
getStateManager().moveToRestState();
}
+ return Unit.INSTANCE;
});
} else {
getStateManager().goToState(NORMAL);
@@ -665,7 +672,7 @@
addMultiWindowModeChangedListener(mDepthController);
initUnfoldTransitionProgressProvider();
if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
- mViewCapture = SettingsAwareViewCapture.getInstance(this).startCapture(getWindow());
+ mViewCapture = ViewCaptureFactory.getInstance(this).startCapture(getWindow());
}
getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
QuickstepOnboardingPrefs.setup(this);
@@ -674,6 +681,14 @@
}
@Override
+ protected boolean initDeviceProfile(InvariantDeviceProfile idp) {
+ final boolean ret = super.initDeviceProfile(idp);
+ mDeviceProfile.isPredictiveBackSwipe =
+ getApplicationInfo().isOnBackInvokedCallbackEnabled();
+ return ret;
+ }
+
+ @Override
public void startSplitSelection(SplitSelectSource splitSelectSource) {
RecentsView recentsView = getOverviewPanel();
// Check if there is already an instance of this app running, if so, initiate the split
@@ -741,6 +756,9 @@
@Override
public boolean isSplitSelectionActive() {
+ if (mSplitSelectStateController == null) {
+ return false;
+ }
return mSplitSelectStateController.isSplitSelectActive();
}
@@ -844,6 +862,28 @@
}
@Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return tryHandleBackKey(event) || super.dispatchKeyEvent(event);
+ }
+
+ // TODO (b/267248420) Once the recents input consumer has been removed, there is no need to
+ // handle the back key specially.
+ private boolean tryHandleBackKey(KeyEvent event) {
+ // Unlike normal activity, recents can receive input event from InputConsumer, so the input
+ // event won't go through ViewRootImpl#InputStage#onProcess.
+ // So when receive back key, try to do the same check thing in
+ // ViewRootImpl#NativePreImeInputStage#onProcess
+ if (!Utilities.ATLEAST_U || !enablePredictiveBackGesture()
+ || event.getKeyCode() != KeyEvent.KEYCODE_BACK
+ || event.getAction() != KeyEvent.ACTION_UP || event.isCanceled()) {
+ return false;
+ }
+
+ getOnBackAnimationCallback().onBackInvoked();
+ return true;
+ }
+
+ @Override
protected void registerBackDispatcher() {
if (!enablePredictiveBackGesture()) {
super.registerBackDispatcher();
@@ -946,15 +986,13 @@
@Override
public void setResumed() {
- if (isDesktopModeSupported()) {
- DesktopVisibilityController controller = mDesktopVisibilityController;
- if (controller != null && controller.areFreeformTasksVisible()
- && !controller.isRecentsGestureInProgress()) {
- // Return early to skip setting activity to appear as resumed
- // TODO(b/255649902): shouldn't be needed when we have a separate launcher state
- // for desktop that we can use to control other parts of launcher
- return;
- }
+ if (!enableDesktopWindowingWallpaperActivity()
+ && mDesktopVisibilityController != null
+ && mDesktopVisibilityController.areDesktopTasksVisible()
+ && !mDesktopVisibilityController.isRecentsGestureInProgress()) {
+ // Return early to skip setting activity to appear as resumed
+ // TODO: b/333533253 - Remove after flag rollout
+ return;
}
super.setResumed();
}
@@ -1013,6 +1051,7 @@
getMainExecutor(),
getMainThreadHandler(),
/* backgroundExecutor= */ UI_HELPER_EXECUTOR,
+ /* bgHandler= */ UI_HELPER_EXECUTOR.getHandler(),
/* tracingTagPrefix= */ "launcher",
getSystemService(DisplayManager.class)
);
@@ -1032,7 +1071,7 @@
}
private void initUnfoldAnimationController(UnfoldTransitionProgressProvider progressProvider,
- RotationChangeProvider rotationChangeProvider) {
+ @UnfoldMain RotationChangeProvider rotationChangeProvider) {
mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
/* launcher= */ this,
getWindowManager(),
@@ -1067,8 +1106,9 @@
.playPlaceholderDismissAnim(this, splitDismissEvent);
}
- public <T extends OverviewActionsView> T getActionsView() {
- return (T) mActionsView;
+ @Override
+ public OverviewActionsView<?> getActionsView() {
+ return mActionsView;
}
@Override
@@ -1078,7 +1118,7 @@
}
@Override
- protected void collectStateHandlers(List<StateHandler> out) {
+ public void collectStateHandlers(List<StateHandler<LauncherState>> out) {
super.collectStateHandlers(out);
out.add(getDepthController());
out.add(new RecentsViewStateController(this));
@@ -1088,6 +1128,7 @@
return mDepthController;
}
+ @Nullable
public DesktopVisibilityController getDesktopVisibilityController() {
return mDesktopVisibilityController;
}
@@ -1190,6 +1231,7 @@
switch (info.container) {
case Favorites.CONTAINER_DESKTOP:
case Favorites.CONTAINER_HOTSEAT:
+ case Favorites.CONTAINER_PRIVATESPACE:
// Fall through and continue it's on the workspace (we don't support swiping back
// to other containers like all apps or the hotseat predictions (which can change)
break;
@@ -1234,7 +1276,7 @@
if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
getDragLayer().recreateControllers();
if (mActionsView != null) {
- mActionsView.updateVerticalMargin(info.navigationMode);
+ mActionsView.updateVerticalMargin(info.getNavigationMode());
}
}
}
@@ -1295,9 +1337,9 @@
}
@Override
- public boolean areFreeformTasksVisible() {
+ public boolean areDesktopTasksVisible() {
if (mDesktopVisibilityController != null) {
- return mDesktopVisibilityController.areFreeformTasksVisible();
+ return mDesktopVisibilityController.areDesktopTasksVisible();
}
return false;
}
@@ -1346,7 +1388,8 @@
* Launches two apps as an app pair.
*/
public void launchAppPair(AppPairIcon appPairIcon) {
- mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon);
+ mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon,
+ CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE);
}
public boolean canStartHomeSafely() {
@@ -1379,25 +1422,25 @@
}
private static final class LauncherTaskViewController extends
- TaskViewTouchController<Launcher> {
+ TaskViewTouchController<QuickstepLauncher> {
- LauncherTaskViewController(Launcher activity) {
+ LauncherTaskViewController(QuickstepLauncher activity) {
super(activity);
}
@Override
protected boolean isRecentsInteractive() {
- return mActivity.isInState(OVERVIEW) || mActivity.isInState(OVERVIEW_MODAL_TASK);
+ return mContainer.isInState(OVERVIEW) || mContainer.isInState(OVERVIEW_MODAL_TASK);
}
@Override
protected boolean isRecentsModal() {
- return mActivity.isInState(OVERVIEW_MODAL_TASK);
+ return mContainer.isInState(OVERVIEW_MODAL_TASK);
}
@Override
protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
- mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
+ mContainer.getStateManager().setCurrentUserControlledAnimation(animController);
}
}
@@ -1420,6 +1463,10 @@
if (mHotseatPredictionController != null) {
mHotseatPredictionController.dump(prefix, writer);
}
+ PredictionRowView<?> predictionRowView =
+ getAppsView().getFloatingHeaderView().findFixedRowByType(
+ PredictionRowView.class);
+ predictionRowView.dump(prefix, writer);
}
@Override
@@ -1438,4 +1485,9 @@
}
return super.onCreateView(parent, name, context, attrs);
}
+
+ @Override
+ public boolean isRecentsViewVisible() {
+ return getStateManager().getState().isRecentsViewVisible;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index e3ff281..01d5ff0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.uioverrides;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -26,13 +27,12 @@
import android.util.SparseArray;
import android.widget.RemoteViews;
+import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -101,7 +101,7 @@
new ArrayList<>(h.mProviderChangedListeners).forEach(
ProviderChangedListener::notifyWidgetProvidersChanged))),
UI_HELPER_EXECUTOR.getLooper());
- if (!WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (WIDGETS_ENABLED) {
sWidgetHost.startListening();
}
}
@@ -195,7 +195,7 @@
*/
@Override
public void stopListening() {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
return;
}
@@ -265,6 +265,14 @@
}
}
+ /**
+ * Clears all the internal widget views excluding the update listeners
+ */
+ @Override
+ public void clearWidgetViews() {
+ mViews.clear();
+ }
+
private static class QuickstepWidgetHolderListener
implements AppWidgetHost.AppWidgetHostListener {
@@ -288,21 +296,21 @@
}
@Override
- @WorkerThread
+ @AnyThread
public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
mRemoteViews = null;
executeOnMainExecutor(KEY_PROVIDER_UPDATE, info);
}
@Override
- @WorkerThread
+ @AnyThread
public void updateAppWidget(@Nullable RemoteViews views) {
mRemoteViews = views;
executeOnMainExecutor(KEY_VIEWS_UPDATE, mRemoteViews);
}
@Override
- @WorkerThread
+ @AnyThread
public void onViewDataChanged(int viewId) {
executeOnMainExecutor(KEY_VIEW_DATA_CHANGED, viewId);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index e6a115a..235ec7b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
-import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
@@ -41,6 +40,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
@@ -67,7 +67,7 @@
@Override
public void setState(@NonNull LauncherState state) {
super.setState(state);
- if (state.overviewUi) {
+ if (state.isRecentsViewVisible) {
mRecentsView.updateEmptyMessage();
} else {
mRecentsView.resetTaskVisuals();
@@ -76,7 +76,7 @@
mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
// In Overview, we may be layering app surfaces behind Launcher, so we need to notify
// DepthController to prevent optimizations which might occlude the layers behind
- mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi);
+ mLauncher.getDepthController().setHasContentBehindLauncher(state.isRecentsViewVisible);
PendingAnimation builder =
new PendingAnimation(state.getTransitionDuration(mLauncher, true));
@@ -89,7 +89,7 @@
@NonNull StateAnimationConfig config, @NonNull PendingAnimation builder) {
super.setStateWithAnimationInternal(toState, config, builder);
- if (toState.overviewUi) {
+ if (toState.isRecentsViewVisible) {
// While animating into recents, update the visible task data as needed
builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
mRecentsView.updateEmptyMessage();
@@ -107,7 +107,8 @@
// In Overview, we may be layering app surfaces behind Launcher, so we need to notify
// DepthController to prevent optimizations which might occlude the layers behind
builder.addListener(AnimatorListeners.forSuccessCallback(() ->
- mLauncher.getDepthController().setHasContentBehindLauncher(toState.overviewUi)));
+ mLauncher.getDepthController().setHasContentBehindLauncher(
+ toState.isRecentsViewVisible)));
handleSplitSelectionState(toState, builder, /* animate */true);
@@ -133,7 +134,7 @@
// Create transition animations to split select
RecentsPagedOrientationHandler orientationHandler =
((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
- Pair<FloatProperty, FloatProperty> taskViewsFloat =
+ Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat =
orientationHandler.getSplitSelectTaskOffset(
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
mLauncher.getDeviceProfile());
@@ -168,7 +169,7 @@
clearAllButtonAlpha, LINEAR);
float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0;
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
- MULTI_PROPERTY_VALUE, overviewButtonAlpha, config.getInterpolator(
+ AnimatedFloat.VALUE, overviewButtonAlpha, config.getInterpolator(
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
new file mode 100644
index 0000000..0469636
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides
+
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.IIntentReceiver
+import android.content.IIntentSender
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.os.Bundle
+import android.os.Flags.allowPrivateProfile
+import android.os.IBinder
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.ArrayMap
+import android.widget.Toast
+import android.window.RemoteTransition
+import com.android.launcher3.Flags.enablePrivateSpace
+import com.android.launcher3.Flags.enablePrivateSpaceInstallShortcut
+import com.android.launcher3.Flags.privateSpaceAppInstallerButton
+import com.android.launcher3.Flags.privateSpaceSysAppsSeparation
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.proxy.ProxyActivityStarter
+import com.android.launcher3.util.ApiWrapper
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.StartActivityParams
+import com.android.launcher3.util.UserIconInfo
+import com.android.quickstep.util.FadeOutRemoteTransition
+
+/** A wrapper for the hidden API calls */
+open class SystemApiWrapper(context: Context?) : ApiWrapper(context) {
+
+ override fun getPersons(si: ShortcutInfo) = si.persons ?: Utilities.EMPTY_PERSON_ARRAY
+
+ override fun getActivityOverrides(): Map<String, LauncherActivityInfo> =
+ mContext.getSystemService(LauncherApps::class.java)!!.activityOverrides
+
+ override fun createFadeOutAnimOptions(): ActivityOptions =
+ ActivityOptions.makeBasic().apply {
+ remoteTransition = RemoteTransition(FadeOutRemoteTransition())
+ }
+
+ override fun queryAllUsers(): Map<UserHandle, UserIconInfo> {
+ if (!allowPrivateProfile() || !enablePrivateSpace()) {
+ return super.queryAllUsers()
+ }
+ val users = ArrayMap<UserHandle, UserIconInfo>()
+ mContext.getSystemService(UserManager::class.java)!!.userProfiles?.forEach { user ->
+ mContext.getSystemService(LauncherApps::class.java)!!.getLauncherUserInfo(user)?.apply {
+ users[user] =
+ UserIconInfo(
+ user,
+ when (userType) {
+ UserManager.USER_TYPE_PROFILE_MANAGED -> UserIconInfo.TYPE_WORK
+ UserManager.USER_TYPE_PROFILE_CLONE -> UserIconInfo.TYPE_CLONED
+ UserManager.USER_TYPE_PROFILE_PRIVATE -> UserIconInfo.TYPE_PRIVATE
+ else -> UserIconInfo.TYPE_MAIN
+ },
+ userSerialNumber.toLong()
+ )
+ }
+ }
+ return users
+ }
+
+ override fun getPreInstalledSystemPackages(user: UserHandle): List<String> =
+ if (allowPrivateProfile() && enablePrivateSpace() && privateSpaceSysAppsSeparation())
+ mContext
+ .getSystemService(LauncherApps::class.java)!!
+ .getPreInstalledSystemPackages(user)
+ else ArrayList()
+
+ override fun getAppMarketActivityIntent(packageName: String, user: UserHandle): Intent =
+ if (
+ allowPrivateProfile() &&
+ enablePrivateSpace() &&
+ (privateSpaceAppInstallerButton() || enablePrivateSpaceInstallShortcut())
+ )
+ ProxyActivityStarter.getLaunchIntent(
+ mContext,
+ StartActivityParams(null as PendingIntent?, 0).apply {
+ intentSender =
+ mContext
+ .getSystemService(LauncherApps::class.java)!!
+ .getAppMarketActivityIntent(packageName, user)
+ options =
+ ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ .toBundle()
+ requireActivityResult = false
+ }
+ )
+ else super.getAppMarketActivityIntent(packageName, user)
+
+ /** Returns an intent which can be used to open Private Space Settings. */
+ override fun getPrivateSpaceSettingsIntent(): Intent? =
+ if (allowPrivateProfile() && enablePrivateSpace())
+ ProxyActivityStarter.getLaunchIntent(
+ mContext,
+ StartActivityParams(null as PendingIntent?, 0).apply {
+ intentSender =
+ mContext
+ .getSystemService(LauncherApps::class.java)
+ ?.privateSpaceSettingsIntent ?: return null
+ options =
+ ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ .toBundle()
+ requireActivityResult = false
+ }
+ )
+ else null
+
+ override fun isNonResizeableActivity(lai: LauncherActivityInfo) =
+ lai.activityInfo.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE
+
+ /**
+ * 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.
+ */
+ override fun assignDefaultHomeRole(context: Context) {
+ val roleManager = context.getSystemService(RoleManager::class.java)
+ if (
+ (roleManager!!.isRoleAvailable(RoleManager.ROLE_HOME) &&
+ !roleManager.isRoleHeld(RoleManager.ROLE_HOME))
+ ) {
+ val roleRequestIntent = roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME)
+ val pendingIntent =
+ PendingIntent(
+ object : IIntentSender.Stub() {
+ override fun send(
+ code: Int,
+ intent: Intent,
+ resolvedType: String?,
+ allowlistToken: IBinder?,
+ finishedReceiver: IIntentReceiver?,
+ requiredPermission: String?,
+ options: Bundle?
+ ) {
+ if (code != -1) {
+ Executors.MAIN_EXECUTOR.execute {
+ Toast.makeText(
+ context,
+ context.getString(
+ R.string.set_default_home_app,
+ context.getString(R.string.derived_app_name)
+ ),
+ Toast.LENGTH_LONG
+ )
+ .show()
+ }
+ }
+ }
+ }
+ )
+ val params = StartActivityParams(pendingIntent, 0)
+ params.intent = roleRequestIntent
+ context.startActivity(ProxyActivityStarter.getLaunchIntent(context, params))
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
deleted file mode 100644
index 630ef39..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides.flags;
-
-import static com.android.launcher3.uioverrides.flags.FlagsFactory.TEAMFOOD_FLAG;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.config.FeatureFlags.BooleanFlag;
-import com.android.launcher3.config.FeatureFlags.FlagState;
-
-class DebugFlag extends BooleanFlag {
-
- public final String key;
- public final String description;
-
- @NonNull
- public final FlagState defaultValue;
-
- DebugFlag(String key, String description, FlagState defaultValue, boolean currentValue) {
- super(currentValue);
- this.key = key;
- this.defaultValue = defaultValue;
- this.description = description;
- }
-
- /**
- * Returns {@code true} if this flag's value has been modified from its default.
- * <p>
- * This helps to identify which flags have been toggled in log dumps and bug reports to
- * further help triaging and debugging.
- */
- boolean currentValueModified() {
- switch (defaultValue) {
- case ENABLED: return !get();
- case TEAMFOOD: return TEAMFOOD_FLAG.get() != get();
- case DISABLED: return get();
- default: return true;
- }
- }
-
- @Override
- public String toString() {
- return key + ": defaultValue=" + defaultValue + ", mCurrentValue=" + get();
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
new file mode 100644
index 0000000..dc6365b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
@@ -0,0 +1,573 @@
+/*
+ * 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.uioverrides.flags
+
+import android.app.PendingIntent
+import android.app.blob.BlobHandle.createWithSha256
+import android.app.blob.BlobStoreManager
+import android.content.Context
+import android.content.IIntentReceiver
+import android.content.IIntentSender.Stub
+import android.content.Intent
+import android.content.Intent.ACTION_CREATE_DOCUMENT
+import android.content.Intent.ACTION_OPEN_DOCUMENT
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Bundle
+import android.os.IBinder
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream
+import android.provider.DeviceConfig
+import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
+import android.provider.Settings.Secure
+import android.text.Html
+import android.util.AttributeSet
+import android.util.Base64
+import android.util.Base64.NO_PADDING
+import android.util.Base64.NO_WRAP
+import android.view.inputmethod.EditorInfo
+import android.widget.TextView
+import android.widget.Toast
+import androidx.core.widget.doAfterTextChanged
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceViewHolder
+import androidx.preference.SwitchPreference
+import com.android.launcher3.AutoInstallsLayout
+import com.android.launcher3.ExtendedEditText
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY
+import com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL
+import com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG
+import com.android.launcher3.R
+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.pm.UserCache
+import com.android.launcher3.proxy.ProxyActivityStarter
+import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR
+import com.android.launcher3.util.LauncherLayoutBuilder
+import com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT
+import com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT
+import com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN
+import com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT
+import com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN
+import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
+import com.android.launcher3.util.PluginManagerWrapper
+import com.android.launcher3.util.StartActivityParams
+import com.android.launcher3.util.UserIconInfo
+import com.android.quickstep.util.DeviceConfigHelper
+import com.android.quickstep.util.DeviceConfigHelper.Companion.NAMESPACE_LAUNCHER
+import com.android.quickstep.util.DeviceConfigHelper.DebugInfo
+import com.android.systemui.shared.plugins.PluginEnabler
+import com.android.systemui.shared.plugins.PluginPrefs
+import java.io.OutputStreamWriter
+import java.security.MessageDigest
+import java.util.Locale
+import java.util.concurrent.Executor
+
+/** Helper class to generate UI for Device Config */
+class DevOptionsUiHelper(c: Context, attr: AttributeSet?) : PreferenceGroup(c, attr) {
+
+ init {
+ layoutResource = R.layout.developer_options_top_bar
+ isPersistent = false
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ // Initialize search
+ (holder.findViewById(R.id.filter_box) as TextView?)?.doAfterTextChanged {
+ val query: String = it.toString().lowercase(Locale.getDefault()).replace("_", " ")
+ filterPreferences(query, this)
+
+ // Always keep myself visible
+ [email protected] = true
+ }
+ }
+
+ private fun filterPreferences(query: String, pg: PreferenceGroup) {
+ val count = pg.preferenceCount
+ var visible = false
+ for (i in 0 until count) {
+ val preference = pg.getPreference(i)
+ if (preference is PreferenceGroup) {
+ filterPreferences(query, preference)
+ } else {
+ val title =
+ preference.title.toString().lowercase(Locale.getDefault()).replace("_", " ")
+ preference.isVisible = query.isEmpty() || title.contains(query)
+ }
+ visible = visible or preference.isVisible
+ }
+ pg.isVisible = visible
+ }
+
+ override fun onAttached() {
+ super.onAttached()
+
+ removeAll()
+ inflateServerFlags(newCategory("Server flags", "Long press to reset"))
+ if (PluginPrefs.hasPlugins(context)) {
+ inflatePluginPrefs(newCategory("Plugins"))
+ }
+ addIntentTargets()
+ addOnboardingPrefsCategory()
+ addLayoutSharePref()
+ }
+
+ private fun newCategory(titleText: String, subTitleText: String? = null) =
+ PreferenceCategory(context).apply {
+ title = titleText
+ summary = subTitleText
+ [email protected](this)
+ }
+
+ /** Inflates preferences for all server flags in the provider PreferenceGroup */
+ private fun inflateServerFlags(parent: PreferenceGroup) {
+ val prefs = DeviceConfigHelper.prefs
+ // Sort the keys in the order of modified first followed by natural order
+ val allProps =
+ DeviceConfigHelper.allProps.values
+ .toList()
+ .sortedWith(
+ Comparator.comparingInt { prop: DebugInfo<*> ->
+ if (prefs.contains(prop.key)) 0 else 1
+ }
+ .thenComparing { prop: DebugInfo<*> -> prop.key }
+ )
+
+ // First add boolean flags
+ allProps.forEach {
+ if (it.isInt) return@forEach
+ val info = it as DebugInfo<Boolean>
+
+ val preference = CustomSwitchPref { holder, pref ->
+ holder.itemView.setOnLongClickListener {
+ prefs.edit().remove(pref.key).apply()
+ pref.setChecked(info.getBoolValue())
+ summary = info.getSummary()
+ true
+ }
+ }
+ preference.key = info.key
+ preference.isPersistent = false
+ preference.title = info.key
+ preference.summary = info.getSummary()
+ preference.setChecked(prefs.getBoolean(info.key, info.getBoolValue()))
+ preference.setOnPreferenceChangeListener { _, newVal ->
+ prefs.edit().putBoolean(info.key, newVal as Boolean).apply()
+ preference.summary = info.getSummary()
+ true
+ }
+ parent.addPreference(preference)
+ }
+
+ // Apply Int flags
+ allProps.forEach {
+ if (!it.isInt) return@forEach
+ val info = it as DebugInfo<Int>
+
+ val preference = CustomPref { holder, pref ->
+ val textView = holder.findViewById(R.id.pref_edit_text) as ExtendedEditText
+ textView.setText(info.getIntValueAsString())
+ textView.setOnEditorActionListener { _, actionId, _ ->
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ prefs.edit().putInt(pref.key, textView.text.toString().toInt()).apply()
+ pref.summary = info.getSummary()
+ true
+ }
+ false
+ }
+ textView.setOnBackKeyListener {
+ textView.setText(info.getIntValueAsString())
+ true
+ }
+
+ holder.itemView.setOnLongClickListener {
+ prefs.edit().remove(pref.key).apply()
+ textView.setText(info.getIntValueAsString())
+ pref.summary = info.getSummary()
+ true
+ }
+ }
+ preference.key = info.key
+ preference.isPersistent = false
+ preference.title = info.key
+ preference.summary = info.getSummary()
+ preference.widgetLayoutResource = R.layout.develop_options_edit_text
+ parent.addPreference(preference)
+ }
+ }
+
+ /**
+ * Returns the summary to show the description and whether the flag overrides the default value.
+ */
+ private fun DebugInfo<*>.getSummary() =
+ Html.fromHtml(
+ (if (DeviceConfigHelper.prefs.contains(this.key))
+ "<font color='red'><b>[OVERRIDDEN]</b></font><br>"
+ else "") + this.desc
+ )
+
+ private fun DebugInfo<Boolean>.getBoolValue() =
+ DeviceConfigHelper.prefs.getBoolean(
+ this.key,
+ DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, this.key, this.valueInCode)
+ )
+
+ private fun DebugInfo<Int>.getIntValueAsString() =
+ DeviceConfigHelper.prefs
+ .getInt(this.key, DeviceConfig.getInt(NAMESPACE_LAUNCHER, this.key, this.valueInCode))
+ .toString()
+
+ /**
+ * Inflates the preferences for plugins
+ *
+ * A single pref is added for a plugin-group. A plugin-group is a collection of plugins in a
+ * single apk which have the same android:process tags defined. The apk should also hold the
+ * PLUGIN_PERMISSION. We collect all the plugin intents which Launcher listens for and fetch all
+ * corresponding plugins on the device. When a plugin-group is enabled/disabled we also need to
+ * notify the pluginManager manually since the broadcast-mechanism only works in sysui process
+ */
+ private fun inflatePluginPrefs(parent: PreferenceGroup) {
+ val manager = PluginManagerWrapper.INSTANCE[context] as PluginManagerWrapperImpl
+ val pm = context.packageManager
+
+ val pluginPermissionApps =
+ pm.getPackagesHoldingPermissions(
+ arrayOf(PLUGIN_PERMISSION),
+ PackageManager.MATCH_DISABLED_COMPONENTS
+ )
+ .map { it.packageName }
+
+ manager.pluginActions
+ .flatMap { action ->
+ pm.queryIntentServices(
+ Intent(action),
+ PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.GET_RESOLVED_FILTER
+ )
+ .filter { pluginPermissionApps.contains(it.serviceInfo.packageName) }
+ }
+ .groupBy { "${it.serviceInfo.packageName}-${it.serviceInfo.processName}" }
+ .values
+ .forEach { infoList ->
+ val pluginInfo = infoList[0]!!
+ val pluginUri = Uri.fromParts("package", pluginInfo.serviceInfo.packageName, null)
+
+ CustomSwitchPref { holder, _ ->
+ holder.itemView.setOnLongClickListener {
+ context.startActivity(
+ Intent(ACTION_APPLICATION_DETAILS_SETTINGS, pluginUri)
+ )
+ true
+ }
+ }
+ .apply {
+ isPersistent = true
+ title = pluginInfo.loadLabel(pm)
+ isChecked =
+ infoList.all {
+ manager.pluginEnabler.isEnabled(it.serviceInfo.componentName)
+ }
+ summary =
+ infoList
+ .map { it.filter }
+ .filter { it?.countActions() ?: 0 > 0 }
+ .joinToString(prefix = "Plugins: ") {
+ it.getAction(0)
+ .replace("com.android.systemui.action.PLUGIN_", "")
+ .replace("com.android.launcher3.action.PLUGIN_", "")
+ }
+
+ setOnPreferenceChangeListener { _, newVal ->
+ val disabledState =
+ if (newVal as Boolean) PluginEnabler.ENABLED
+ else PluginEnabler.DISABLED_MANUALLY
+ infoList.forEach {
+ manager.pluginEnabler.setDisabled(
+ it.serviceInfo.componentName,
+ disabledState
+ )
+ }
+ manager.notifyChange(Intent(Intent.ACTION_PACKAGE_CHANGED, pluginUri))
+ true
+ }
+
+ parent.addPreference(this)
+ }
+ }
+ }
+
+ private fun addIntentTargets() {
+ val launchSandboxIntent =
+ Intent("com.android.quickstep.action.GESTURE_SANDBOX")
+ .setPackage(context.packageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ newCategory("Gesture Navigation Sandbox").apply {
+ addPreference(
+ Preference(context).apply {
+ title = "Launch Gesture Tutorial Steps menu"
+ intent = Intent(launchSandboxIntent).putExtra("use_tutorial_menu", true)
+ }
+ )
+ addPreference(
+ Preference(context).apply {
+ title = "Launch Back Tutorial"
+ intent =
+ Intent(launchSandboxIntent)
+ .putExtra("use_tutorial_menu", false)
+ .putExtra("tutorial_steps", arrayOf("BACK_NAVIGATION"))
+ }
+ )
+ addPreference(
+ Preference(context).apply {
+ title = "Launch Home Tutorial"
+ intent =
+ Intent(launchSandboxIntent)
+ .putExtra("use_tutorial_menu", false)
+ .putExtra("tutorial_steps", arrayOf("HOME_NAVIGATION"))
+ }
+ )
+ addPreference(
+ Preference(context).apply {
+ title = "Launch Overview Tutorial"
+ intent =
+ Intent(launchSandboxIntent)
+ .putExtra("use_tutorial_menu", false)
+ .putExtra("tutorial_steps", arrayOf("OVERVIEW_NAVIGATION"))
+ }
+ )
+ }
+
+ newCategory("Other activity targets").apply {
+ addPreference(
+ Preference(context).apply {
+ title = "Launch Secondary Display"
+ intent =
+ Intent(context, SecondaryDisplayLauncher::class.java)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ )
+ }
+ }
+
+ private fun addOnboardingPrefsCategory() {
+ newCategory("Onboarding Flows").apply {
+ summary = "Reset these if you want to see the education again."
+ addOnboardPref(
+ "All Apps Bounce",
+ HOME_BOUNCE_SEEN.sharedPrefKey,
+ HOME_BOUNCE_COUNT.sharedPrefKey
+ )
+ addOnboardPref(
+ "Hybrid Hotseat Education",
+ HOTSEAT_DISCOVERY_TIP_COUNT.sharedPrefKey,
+ HOTSEAT_LONGPRESS_TIP_SEEN.sharedPrefKey
+ )
+ addOnboardPref("Taskbar Education", TASKBAR_EDU_TOOLTIP_STEP.sharedPrefKey)
+ addOnboardPref("All Apps Visited Count", ALL_APPS_VISITED_COUNT.sharedPrefKey)
+ }
+ }
+
+ private fun PreferenceCategory.addOnboardPref(title: String, vararg keys: String) =
+ this.addPreference(
+ Preference(context).also {
+ it.title = title
+ it.summary = "Tap to reset"
+ it.setOnPreferenceClickListener { _ ->
+ LauncherPrefs.getPrefs(context)
+ .edit()
+ .apply { keys.forEach { key -> remove(key) } }
+ .apply()
+ Toast.makeText(context, "Reset $title", Toast.LENGTH_SHORT).show()
+ true
+ }
+ }
+ )
+
+ private fun addLayoutSharePref() {
+ val model = LauncherAppState.getInstance(context).model
+ val category = newCategory("Workspace grid layout")
+ Preference(context).apply {
+ title = "Export"
+ intent =
+ createUriPickerIntent(ACTION_CREATE_DOCUMENT, MAIN_EXECUTOR) { uri ->
+ model.enqueueModelUpdateTask { _, dataModel, _ ->
+ val builder = LauncherLayoutBuilder()
+ dataModel.workspaceItems.forEach { info ->
+ val loc =
+ when (info.container) {
+ CONTAINER_DESKTOP ->
+ builder.atWorkspace(info.cellX, info.cellY, info.screenId)
+ CONTAINER_HOTSEAT -> builder.atHotseat(info.screenId)
+ else -> return@forEach
+ }
+ loc.addItem(info)
+ }
+ dataModel.appWidgets.forEach { info ->
+ builder.atWorkspace(info.cellX, info.cellY, info.screenId).addItem(info)
+ }
+
+ context.contentResolver.openOutputStream(uri).use { os ->
+ builder.build(OutputStreamWriter(os))
+ }
+
+ MAIN_EXECUTOR.execute {
+ Toast.makeText(context, "File saved", Toast.LENGTH_LONG).show()
+ }
+ }
+ }
+ category.addPreference(this)
+ }
+
+ Preference(context).apply {
+ title = "Import"
+ intent =
+ createUriPickerIntent(ACTION_OPEN_DOCUMENT, ORDERED_BG_EXECUTOR) { uri ->
+ val resolver = context.contentResolver
+ val data =
+ resolver.openInputStream(uri).use { stream ->
+ stream?.readAllBytes() ?: return@createUriPickerIntent
+ }
+
+ val digest = MessageDigest.getInstance("SHA-256").digest(data)
+ val handle = createWithSha256(digest, LAYOUT_DIGEST_LABEL, 0, LAYOUT_DIGEST_TAG)
+ val blobManager = context.getSystemService(BlobStoreManager::class.java)!!
+
+ blobManager.openSession(blobManager.createSession(handle)).use { session ->
+ AutoCloseOutputStream(session.openWrite(0, -1)).use { it.write(data) }
+ session.allowPublicAccess()
+
+ session.commit(ORDERED_BG_EXECUTOR) {
+ val key = Base64.encodeToString(digest, NO_WRAP or NO_PADDING)
+ Secure.putString(resolver, LAYOUT_DIGEST_KEY, key)
+
+ MODEL_EXECUTOR.submit { model.modelDbController.createEmptyDB() }.get()
+ MAIN_EXECUTOR.submit { model.forceReload() }.get()
+ MODEL_EXECUTOR.submit {}.get()
+ Secure.putString(resolver, LAYOUT_DIGEST_KEY, null)
+ }
+ }
+ }
+ category.addPreference(this)
+ }
+ }
+
+ private fun LauncherLayoutBuilder.ItemTarget.addItem(info: ItemInfo) {
+ val userType: String? =
+ when (UserCache.INSTANCE.get(context).getUserInfo(info.user).type) {
+ UserIconInfo.TYPE_WORK -> AutoInstallsLayout.USER_TYPE_WORK
+ UserIconInfo.TYPE_CLONED -> AutoInstallsLayout.USER_TYPE_CLONED
+ else -> null
+ }
+ when (info.itemType) {
+ ITEM_TYPE_APPLICATION ->
+ info.targetComponent?.let { c -> putApp(c.packageName, c.className, userType) }
+ ITEM_TYPE_DEEP_SHORTCUT ->
+ ShortcutKey.fromItemInfo(info).let { key ->
+ putShortcut(key.packageName, key.id, userType)
+ }
+ ITEM_TYPE_FOLDER ->
+ (info as FolderInfo).let { folderInfo ->
+ putFolder(folderInfo.title?.toString() ?: "").also { folderBuilder ->
+ folderInfo.getContents().forEach { folderContent ->
+ folderBuilder.addItem(folderContent)
+ }
+ }
+ }
+ ITEM_TYPE_APPWIDGET ->
+ putWidget(
+ (info as LauncherAppWidgetInfo).providerName.packageName,
+ info.providerName.className,
+ info.spanX,
+ info.spanY,
+ userType
+ )
+ }
+ }
+
+ private fun createUriPickerIntent(
+ action: String,
+ executor: Executor,
+ callback: (uri: Uri) -> Unit
+ ): Intent {
+ val pendingIntent =
+ PendingIntent(
+ object : Stub() {
+ override fun send(
+ code: Int,
+ intent: Intent,
+ resolvedType: String?,
+ allowlistToken: IBinder?,
+ finishedReceiver: IIntentReceiver?,
+ requiredPermission: String?,
+ options: Bundle?
+ ) {
+ intent.data?.let { uri -> executor.execute { callback(uri) } }
+ }
+ }
+ )
+ val params = StartActivityParams(pendingIntent, 0)
+ params.intent =
+ Intent(action)
+ .addCategory(Intent.CATEGORY_OPENABLE)
+ .setType("text/xml")
+ .putExtra(Intent.EXTRA_TITLE, "launcher_grid.xml")
+ return ProxyActivityStarter.getLaunchIntent(context, params)
+ }
+
+ private inner class CustomSwitchPref(
+ private val bindCallback: (holder: PreferenceViewHolder, pref: SwitchPreference) -> Unit
+ ) : SwitchPreference(context) {
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ bindCallback.invoke(holder, this)
+ }
+ }
+
+ private inner class CustomPref(
+ private val bindCallback: (holder: PreferenceViewHolder, pref: Preference) -> Unit
+ ) : Preference(context) {
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ bindCallback.invoke(holder, this)
+ }
+ }
+
+ companion object {
+ const val TAG = "DeviceConfigUIHelper"
+
+ const val PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN"
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
deleted file mode 100644
index 369ff14..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
+++ /dev/null
@@ -1,547 +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.uioverrides.flags;
-
-import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
-import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-
-import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
-import static com.android.launcher3.LauncherPrefs.PRIVATE_SPACE_APPS;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_DELAY;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT;
-import static com.android.launcher3.config.FeatureFlags.LPNH_SLOP_PERCENTAGE;
-import static com.android.launcher3.config.FeatureFlags.LPNH_TIMEOUT_MS;
-import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_HIGHLIGHT_KEY;
-import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
-import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
-import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
-import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT;
-import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
-import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT;
-import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN;
-import static com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.provider.Settings;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.Toast;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceDataStore;
-import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.PreferenceGroup;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SeekBarPreference;
-import androidx.preference.SwitchPreference;
-
-import com.android.launcher3.ConstantItem;
-import com.android.launcher3.Flags;
-import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Dev-build only UI allowing developers to toggle flag settings and plugins.
- * See {@link FeatureFlags}.
- */
-public class DeveloperOptionsUI {
-
- private static final String ACTION_PLUGIN_SETTINGS =
- "com.android.systemui.action.PLUGIN_SETTINGS";
- private static final String TAG = "DeveloperOptionsUI";
- private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
-
- private final PreferenceFragmentCompat mFragment;
- private final PreferenceScreen mPreferenceScreen;
-
- private final FlagTogglerPrefUi mFlagTogglerPrefUi;
-
- private PreferenceCategory mPluginsCategory;
-
- public DeveloperOptionsUI(PreferenceFragmentCompat fragment, PreferenceCategory flags) {
- mFragment = fragment;
- mPreferenceScreen = fragment.getPreferenceScreen();
-
- // Add search bar
- View listView = mFragment.getListView();
- ViewGroup parent = (ViewGroup) listView.getParent();
- View topBar = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.developer_options_top_bar, parent, false);
- parent.addView(topBar, parent.indexOfChild(listView));
- initSearch(topBar.findViewById(R.id.filter_box));
-
- mFlagTogglerPrefUi = new FlagTogglerPrefUi(mFragment.requireActivity(),
- topBar.findViewById(R.id.flag_apply_btn));
- mFlagTogglerPrefUi.applyTo(flags);
-
- loadPluginPrefs();
- maybeAddSandboxCategory();
- addOnboardingPrefsCatergory();
- if (FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
- addAllAppsFromOverviewCatergory();
- }
- addCustomLpnhCategory();
- if (Flags.enablePrivateSpace()) {
- addCustomPrivateAppsCategory();
- }
- }
-
- private void filterPreferences(String query, PreferenceGroup pg) {
- int count = pg.getPreferenceCount();
- int hidden = 0;
- for (int i = 0; i < count; i++) {
- Preference preference = pg.getPreference(i);
- if (preference instanceof PreferenceGroup) {
- filterPreferences(query, (PreferenceGroup) preference);
- } else {
- String title = preference.getTitle().toString().toLowerCase().replace("_", " ");
- if (query.isEmpty() || title.contains(query)) {
- preference.setVisible(true);
- } else {
- preference.setVisible(false);
- hidden++;
- }
- }
- }
- pg.setVisible(hidden != count);
- }
-
- private void initSearch(EditText filterBox) {
- filterBox.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
-
- @Override
- public void afterTextChanged(Editable editable) {
- String query = editable.toString().toLowerCase().replace("_", " ");
- filterPreferences(query, mPreferenceScreen);
- }
- });
-
- if (mFragment.getArguments() != null) {
- String filter = mFragment.getArguments().getString(EXTRA_FRAGMENT_HIGHLIGHT_KEY);
- // Normally EXTRA_FRAGMENT_ARG_KEY is used to highlight the preference with the given
- // key. This is a slight variation where we instead filter by the human-readable titles.
- if (filter != null) {
- filterBox.setText(filter);
- }
- }
- }
-
- private PreferenceCategory newCategory(String title) {
- PreferenceCategory category = new PreferenceCategory(getContext());
- category.setOrder(Preference.DEFAULT_ORDER);
- category.setTitle(title);
- mPreferenceScreen.addPreference(category);
- return category;
- }
-
- private Context getContext() {
- return mFragment.requireContext();
- }
-
- private void loadPluginPrefs() {
- if (mPluginsCategory != null) {
- mPreferenceScreen.removePreference(mPluginsCategory);
- }
- if (!PluginManagerWrapper.hasPlugins(getContext())) {
- mPluginsCategory = null;
- return;
- }
- mPluginsCategory = newCategory("Plugins");
-
- PluginManagerWrapper manager = PluginManagerWrapper.INSTANCE.get(getContext());
- Context prefContext = getContext();
- PackageManager pm = getContext().getPackageManager();
-
- Set<String> pluginActions = manager.getPluginActions();
-
- ArrayMap<Pair<String, String>, ArrayList<Pair<String, ResolveInfo>>> plugins =
- new ArrayMap<>();
-
- Set<String> pluginPermissionApps = pm.getPackagesHoldingPermissions(
- new String[]{PLUGIN_PERMISSION}, MATCH_DISABLED_COMPONENTS)
- .stream()
- .map(pi -> pi.packageName)
- .collect(Collectors.toSet());
-
- for (String action : pluginActions) {
- String name = toName(action);
- List<ResolveInfo> result = pm.queryIntentServices(
- new Intent(action), MATCH_DISABLED_COMPONENTS | GET_RESOLVED_FILTER);
- for (ResolveInfo info : result) {
- String packageName = info.serviceInfo.packageName;
- if (!pluginPermissionApps.contains(packageName)) {
- continue;
- }
-
- Pair<String, String> key = Pair.create(packageName, info.serviceInfo.processName);
- if (!plugins.containsKey(key)) {
- plugins.put(key, new ArrayList<>());
- }
- plugins.get(key).add(Pair.create(name, info));
- }
- }
-
- PreferenceDataStore enabler = manager.getPluginEnabler();
- plugins.forEach((key, si) -> {
- String packageName = key.first;
- List<ComponentName> componentNames = si.stream()
- .map(p -> new ComponentName(packageName, p.second.serviceInfo.name))
- .collect(Collectors.toList());
- if (!componentNames.isEmpty()) {
- SwitchPreference pref = new PluginPreference(
- prefContext, si.get(0).second, enabler, componentNames);
- pref.setSummary("Plugins: "
- + si.stream().map(p -> p.first).collect(Collectors.joining(", ")));
- mPluginsCategory.addPreference(pref);
- }
- });
- }
-
- private void maybeAddSandboxCategory() {
- Context context = getContext();
- if (context == null) {
- return;
- }
- Intent launchSandboxIntent =
- new Intent("com.android.quickstep.action.GESTURE_SANDBOX")
- .setPackage(context.getPackageName())
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- if (launchSandboxIntent.resolveActivity(context.getPackageManager()) == null) {
- return;
- }
- PreferenceCategory sandboxCategory = newCategory("Gesture Navigation Sandbox");
- sandboxCategory.setSummary("Learn and practice navigation gestures");
- Preference launchTutorialStepMenuPreference = new Preference(context);
- launchTutorialStepMenuPreference.setKey("launchTutorialStepMenu");
- launchTutorialStepMenuPreference.setTitle("Launch Gesture Tutorial Steps menu");
- launchTutorialStepMenuPreference.setSummary("Select a gesture tutorial step.");
- launchTutorialStepMenuPreference.setIntent(
- new Intent(launchSandboxIntent).putExtra("use_tutorial_menu", true));
-
- sandboxCategory.addPreference(launchTutorialStepMenuPreference);
- Preference launchOnboardingTutorialPreference = new Preference(context);
- launchOnboardingTutorialPreference.setKey("launchOnboardingTutorial");
- launchOnboardingTutorialPreference.setTitle("Launch Onboarding Tutorial");
- launchOnboardingTutorialPreference.setSummary("Learn the basic navigation gestures.");
- launchTutorialStepMenuPreference.setIntent(new Intent(launchSandboxIntent)
- .putExtra("use_tutorial_menu", false)
- .putExtra("tutorial_steps",
- new String[] {
- "HOME_NAVIGATION",
- "BACK_NAVIGATION",
- "OVERVIEW_NAVIGATION"}));
-
- sandboxCategory.addPreference(launchOnboardingTutorialPreference);
- Preference launchBackTutorialPreference = new Preference(context);
- launchBackTutorialPreference.setKey("launchBackTutorial");
- launchBackTutorialPreference.setTitle("Launch Back Tutorial");
- launchBackTutorialPreference.setSummary("Learn how to use the Back gesture");
- launchBackTutorialPreference.setIntent(new Intent(launchSandboxIntent)
- .putExtra("use_tutorial_menu", false)
- .putExtra("tutorial_steps", new String[] {"BACK_NAVIGATION"}));
-
- sandboxCategory.addPreference(launchBackTutorialPreference);
- Preference launchHomeTutorialPreference = new Preference(context);
- launchHomeTutorialPreference.setKey("launchHomeTutorial");
- launchHomeTutorialPreference.setTitle("Launch Home Tutorial");
- launchHomeTutorialPreference.setSummary("Learn how to use the Home gesture");
- launchHomeTutorialPreference.setIntent(new Intent(launchSandboxIntent)
- .putExtra("use_tutorial_menu", false)
- .putExtra("tutorial_steps", new String[] {"HOME_NAVIGATION"}));
-
- sandboxCategory.addPreference(launchHomeTutorialPreference);
- Preference launchOverviewTutorialPreference = new Preference(context);
- launchOverviewTutorialPreference.setKey("launchOverviewTutorial");
- launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
- launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
- launchOverviewTutorialPreference.setIntent(new Intent(launchSandboxIntent)
- .putExtra("use_tutorial_menu", false)
- .putExtra("tutorial_steps", new String[] {"OVERVIEW_NAVIGATION"}));
-
- sandboxCategory.addPreference(launchOverviewTutorialPreference);
- Preference launchSecondaryDisplayPreference = new Preference(context);
- launchSecondaryDisplayPreference.setKey("launchSecondaryDisplay");
- launchSecondaryDisplayPreference.setTitle("Launch Secondary Display");
- launchSecondaryDisplayPreference.setSummary("Launch secondary display activity");
- launchSecondaryDisplayPreference.setIntent(
- new Intent(context, SecondaryDisplayLauncher.class));
-
- }
-
- private void addOnboardingPrefsCatergory() {
- PreferenceCategory onboardingCategory = newCategory("Onboarding Flows");
- onboardingCategory.setSummary("Reset these if you want to see the education again.");
-
- onboardingCategory.addPreference(createOnboardPref("All Apps Bounce",
- HOME_BOUNCE_SEEN.getSharedPrefKey(), HOME_BOUNCE_COUNT.getSharedPrefKey()));
- onboardingCategory.addPreference(createOnboardPref("Hybrid Hotseat Education",
- HOTSEAT_DISCOVERY_TIP_COUNT.getSharedPrefKey(),
- HOTSEAT_LONGPRESS_TIP_SEEN.getSharedPrefKey()));
- onboardingCategory.addPreference(createOnboardPref("Taskbar Education",
- TASKBAR_EDU_TOOLTIP_STEP.getSharedPrefKey()));
- onboardingCategory.addPreference(createOnboardPref("All Apps Visited Count",
- ALL_APPS_VISITED_COUNT.getSharedPrefKey()));
- }
-
- private Preference createOnboardPref(String title, String... keys) {
- Preference onboardingPref = new Preference(getContext());
- onboardingPref.setTitle(title);
- onboardingPref.setSummary("Tap to reset");
- onboardingPref.setOnPreferenceClickListener(preference -> {
- SharedPreferences.Editor sharedPrefsEdit = LauncherPrefs.getPrefs(getContext())
- .edit();
- for (String key : keys) {
- sharedPrefsEdit.remove(key);
- }
- sharedPrefsEdit.apply();
- Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
- return true;
- });
- return onboardingPref;
- }
-
- private void addAllAppsFromOverviewCatergory() {
- PreferenceCategory category = newCategory("All Apps from Overview Config");
- category.addPreference(createSeekBarPreference("Threshold to open All Apps from Overview",
- 105, 500, 100, ALL_APPS_OVERVIEW_THRESHOLD));
- }
-
- private void addCustomLpnhCategory() {
- PreferenceCategory category = newCategory("Long Press Nav Handle Config");
- if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
- category.addPreference(createSeekBarPreference(
- "Slop multiplier (applied to edge slop, "
- + "which is generally already 50% higher than touch slop)",
- 25, 200, 100, LPNH_SLOP_PERCENTAGE));
- category.addPreference(createSeekBarPreference("LPNH timeout",
- 100, 500, 1, LPNH_TIMEOUT_MS));
- }
- if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get()) {
- category.addPreference(
- createSeekBarPreference("Haptic hint start scale",
- 0, 100, 100, LPNH_HAPTIC_HINT_START_SCALE_PERCENT));
- category.addPreference(createSeekBarPreference("Haptic hint end scale",
- 0, 100, 100, LPNH_HAPTIC_HINT_END_SCALE_PERCENT));
- category.addPreference(
- createSeekBarPreference("Haptic hint scale exponent",
- 1, 5, 1, LPNH_HAPTIC_HINT_SCALE_EXPONENT));
- category.addPreference(
- createSeekBarPreference("Haptic hint iterations (12 ms each)",
- 0, 200, 1, LPNH_HAPTIC_HINT_ITERATIONS));
- category.addPreference(createSeekBarPreference("Haptic hint delay (ms)",
- 0, 400, 1, LPNH_HAPTIC_HINT_DELAY));
- }
- }
-
- private void addCustomPrivateAppsCategory() {
- PreferenceCategory category = newCategory("Apps in Private Space Config");
- category.addPreference(createSeekBarPreference(
- "Number of Apps to put in private region", 0, 100, 1, PRIVATE_SPACE_APPS));
- }
-
- private SeekBarPreference createSeekBarPreference(String title, int min,
- int max, int scale, FeatureFlags.IntFlag flag) {
- if (!(flag instanceof IntDebugFlag)) {
- Log.e(TAG, "Cannot create seekbar preference with IntFlag. Use a launcher preference "
- + "flag or pref-backed IntDebugFlag instead");
- return null;
- }
- IntDebugFlag debugflag = (IntDebugFlag) flag;
- if (debugflag.launcherPrefFlag == null) {
- Log.e(TAG, "Cannot create seekbar preference with IntDebugFlag. Use a launcher "
- + "preference flag or pref-backed IntDebugFlag instead");
- return null;
- }
- SeekBarPreference seekBarPref = createSeekBarPreference(title, min, max, scale,
- debugflag.launcherPrefFlag);
- int value = flag.get();
- seekBarPref.setValue(value);
- // For some reason the initial value is not triggering the summary update, so call manually.
- seekBarPref.setSummary(String.valueOf(scale == 1 ? value
- : value / (float) scale));
- return seekBarPref;
- }
-
- /**
- * Create a preference with text and a seek bar. Should be added to a PreferenceCategory.
- *
- * @param title text to show for this seek bar
- * @param min min value for the seek bar
- * @param max max value for the seek bar
- * @param scale how much to divide the value to convert int to float
- * @param launcherPref used to store the current value
- */
- private SeekBarPreference createSeekBarPreference(String title, int min, int max, int scale,
- ConstantItem<Integer> launcherPref) {
- SeekBarPreference seekBarPref = new SeekBarPreference(getContext());
- seekBarPref.setTitle(title);
- seekBarPref.setSingleLineTitle(false);
-
- seekBarPref.setMax(max);
- seekBarPref.setMin(min);
- seekBarPref.setUpdatesContinuously(true);
- seekBarPref.setIconSpaceReserved(false);
- // Don't directly save to shared prefs, use LauncherPrefs instead.
- seekBarPref.setPersistent(false);
- seekBarPref.setOnPreferenceChangeListener((preference, newValue) -> {
- LauncherPrefs.get(getContext()).put(launcherPref, newValue);
- preference.setSummary(String.valueOf(scale == 1 ? newValue
- : (int) newValue / (float) scale));
- mFlagTogglerPrefUi.updateMenu();
- return true;
- });
- int value = LauncherPrefs.get(getContext()).get(launcherPref);
- seekBarPref.setValue(value);
- // For some reason the initial value is not triggering the summary update, so call manually.
- seekBarPref.setSummary(String.valueOf(scale == 1 ? value
- : value / (float) scale));
- return seekBarPref;
- }
-
- private String toName(String action) {
- String str = action.replace("com.android.systemui.action.PLUGIN_", "")
- .replace("com.android.launcher3.action.PLUGIN_", "");
- StringBuilder b = new StringBuilder();
- for (String s : str.split("_")) {
- if (b.length() != 0) {
- b.append(' ');
- }
- b.append(s.substring(0, 1));
- b.append(s.substring(1).toLowerCase());
- }
- return b.toString();
- }
-
- private static class PluginPreference extends SwitchPreference {
- private final String mPackageName;
- private final ResolveInfo mSettingsInfo;
- private final PreferenceDataStore mPluginEnabler;
- private final List<ComponentName> mComponentNames;
-
- PluginPreference(Context prefContext, ResolveInfo pluginInfo,
- PreferenceDataStore pluginEnabler, List<ComponentName> componentNames) {
- super(prefContext);
- PackageManager pm = prefContext.getPackageManager();
- mPackageName = pluginInfo.serviceInfo.applicationInfo.packageName;
- Intent settingsIntent = new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName);
- // If any Settings activity in app has category filters, set plugin action as category.
- List<ResolveInfo> settingsInfos =
- pm.queryIntentActivities(settingsIntent, GET_RESOLVED_FILTER);
- if (pluginInfo.filter != null) {
- for (ResolveInfo settingsInfo : settingsInfos) {
- if (settingsInfo.filter != null && settingsInfo.filter.countCategories() > 0) {
- settingsIntent.addCategory(pluginInfo.filter.getAction(0));
- break;
- }
- }
- }
-
- mSettingsInfo = pm.resolveActivity(settingsIntent, 0);
- mPluginEnabler = pluginEnabler;
- mComponentNames = componentNames;
- setTitle(pluginInfo.loadLabel(pm));
- setChecked(isPluginEnabled());
- setWidgetLayoutResource(R.layout.switch_preference_with_settings);
- }
-
- private boolean isEnabled(ComponentName cn) {
- return mPluginEnabler.getBoolean(pluginEnabledKey(cn), true);
-
- }
-
- private boolean isPluginEnabled() {
- for (ComponentName componentName : mComponentNames) {
- if (!isEnabled(componentName)) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- protected boolean persistBoolean(boolean isEnabled) {
- boolean shouldSendBroadcast = false;
- for (ComponentName componentName : mComponentNames) {
- if (isEnabled(componentName) != isEnabled) {
- mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled);
- shouldSendBroadcast = true;
- }
- }
- if (shouldSendBroadcast) {
- final String pkg = mPackageName;
- final Intent intent = new Intent(PLUGIN_CHANGED,
- pkg != null ? Uri.fromParts("package", pkg, null) : null);
- getContext().sendBroadcast(intent);
- }
- setChecked(isEnabled);
- return true;
- }
-
- @Override
- public void onBindViewHolder(PreferenceViewHolder holder) {
- super.onBindViewHolder(holder);
- boolean hasSettings = mSettingsInfo != null;
- holder.findViewById(R.id.settings).setVisibility(hasSettings ? VISIBLE : GONE);
- holder.findViewById(R.id.divider).setVisibility(hasSettings ? VISIBLE : GONE);
- holder.findViewById(R.id.settings).setOnClickListener(v -> {
- if (hasSettings) {
- v.getContext().startActivity(new Intent().setComponent(
- new ComponentName(mSettingsInfo.activityInfo.packageName,
- mSettingsInfo.activityInfo.name)));
- }
- });
- holder.itemView.setOnLongClickListener(v -> {
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- intent.setData(Uri.fromParts("package", mPackageName, null));
- getContext().startActivity(intent);
- return true;
- });
- }
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
deleted file mode 100644
index 915f4ae..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides.flags;
-
-import com.android.launcher3.config.FeatureFlags.FlagState;
-
-class DeviceFlag extends DebugFlag {
-
- private final boolean mDefaultValueInCode;
-
- DeviceFlag(String key, String description, FlagState defaultValue,
- boolean currentValue, boolean defaultValueInCode) {
- super(key, description, defaultValue, currentValue);
- mDefaultValueInCode = defaultValueInCode;
- }
-
- @Override
- boolean currentValueModified() {
- return super.currentValueModified() || mDefaultValueInCode != get();
- }
-
- @Override
- public String toString() {
- return super.toString() + ", mDefaultValueInCode=" + mDefaultValueInCode;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
deleted file mode 100644
index 4326c67..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
+++ /dev/null
@@ -1,183 +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.uioverrides.flags;
-
-import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
-import static com.android.launcher3.uioverrides.flags.FlagsFactory.TEAMFOOD_FLAG;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Handler;
-import android.os.Process;
-import android.text.Html;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
-
-import androidx.preference.PreferenceDataStore;
-import androidx.preference.PreferenceGroup;
-import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SwitchPreference;
-
-import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
- */
-public final class FlagTogglerPrefUi implements ActivityLifecycleCallbacksAdapter {
-
- private static final String TAG = "FlagTogglerPrefFrag";
-
- private final View mFlagsApplyButton;
- private final Context mContext;
-
- private final PreferenceDataStore mDataStore = new PreferenceDataStore() {
-
- @Override
- public void putBoolean(String key, boolean value) {
- FlagsFactory.getSharedPreferences().edit().putBoolean(key, value).apply();
- updateMenu();
- }
-
- @Override
- public boolean getBoolean(String key, boolean defaultValue) {
- return FlagsFactory.getSharedPreferences().getBoolean(key, defaultValue);
- }
- };
-
- public FlagTogglerPrefUi(Activity activity, View flagsApplyButton) {
- mFlagsApplyButton = flagsApplyButton;
- mContext = mFlagsApplyButton.getContext();
- activity.registerActivityLifecycleCallbacks(this);
-
- mFlagsApplyButton.setOnClickListener(v -> {
- FlagsFactory.getSharedPreferences().edit().commit();
- Log.e(TAG,
- "Killing launcher process " + Process.myPid() + " to apply new flag values");
- System.exit(0);
- });
- }
-
- public void applyTo(PreferenceGroup parent) {
- Set<String> modifiedPrefs = FlagsFactory.getSharedPreferences().getAll().keySet();
- List<DebugFlag> flags = FlagsFactory.getDebugFlags();
- flags.sort((f1, f2) -> {
- // Sort first by any prefs that the user has changed, then alphabetically.
- int changeComparison = Boolean.compare(
- modifiedPrefs.contains(f2.key), modifiedPrefs.contains(f1.key));
- return changeComparison != 0
- ? changeComparison
- : f1.key.compareToIgnoreCase(f2.key);
- });
-
- // Ensure that teamfood flag comes on the top
- if (flags.remove(TEAMFOOD_FLAG)) {
- flags.add(0, (DebugFlag) TEAMFOOD_FLAG);
- }
-
- // For flag overrides we only want to store when the engineer chose to override the
- // flag with a different value than the default. That way, when we flip flags in
- // future, engineers will pick up the new value immediately. To accomplish this, we use a
- // custom preference data store.
- for (DebugFlag flag : flags) {
- SwitchPreference switchPreference = new SwitchPreference(mContext) {
- @Override
- public void onBindViewHolder(PreferenceViewHolder holder) {
- super.onBindViewHolder(holder);
- holder.itemView.setOnLongClickListener(v -> {
- FlagsFactory.getSharedPreferences().edit().remove(flag.key).apply();
- setChecked(getFlagStateFromSharedPrefs(flag));
- updateSummary(this, flag);
- updateMenu();
- return true;
- });
- }
- };
- switchPreference.setKey(flag.key);
- switchPreference.setDefaultValue(FlagsFactory.getEnabledValue(flag.defaultValue));
- switchPreference.setChecked(getFlagStateFromSharedPrefs(flag));
- switchPreference.setTitle(flag.key);
- updateSummary(switchPreference, flag);
- switchPreference.setPreferenceDataStore(mDataStore);
- switchPreference.setOnPreferenceChangeListener((p, v) -> {
- new Handler().post(() -> updateSummary(switchPreference, flag));
- return true;
- });
-
-
- parent.addPreference(switchPreference);
- }
- updateMenu();
- }
-
- /**
- * Updates the summary to show the description and whether the flag overrides the default value.
- */
- private void updateSummary(SwitchPreference switchPreference, DebugFlag flag) {
- String summary = flag.defaultValue == TEAMFOOD
- ? "<font color='blue'><b>[TEAMFOOD]</b> </font>" : "";
- if (FlagsFactory.getSharedPreferences().contains(flag.key)) {
- summary += "<font color='red'><b>[OVERRIDDEN]</b> </font>";
- }
- if (!TextUtils.isEmpty(summary)) {
- summary += "<br>";
- }
- switchPreference.setSummary(Html.fromHtml(summary + flag.description));
- }
-
- public void updateMenu() {
- mFlagsApplyButton.setVisibility(anyChanged() ? View.VISIBLE : View.INVISIBLE);
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
- if (anyChanged()) {
- Toast.makeText(mContext, "Flag won't be applied until you restart launcher",
- Toast.LENGTH_LONG).show();
- }
- }
-
- private boolean getFlagStateFromSharedPrefs(DebugFlag flag) {
- boolean defaultValue = FlagsFactory.getEnabledValue(flag.defaultValue);
- return mDataStore.getBoolean(flag.key, defaultValue);
- }
-
- private int getIntFlagStateFromSharedPrefs(IntDebugFlag flag) {
- LauncherPrefs prefs = LauncherPrefs.get(mContext);
- return flag.launcherPrefFlag == null ? flag.get() : prefs.get(flag.launcherPrefFlag);
- }
-
- private boolean anyChanged() {
- for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
- if (getFlagStateFromSharedPrefs(flag) != flag.get()) {
- return true;
- }
- }
- for (IntDebugFlag flag : FlagsFactory.getIntDebugFlags()) {
- if (getIntFlagStateFromSharedPrefs(flag) != flag.get()) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
deleted file mode 100644
index 686ed64..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides.flags;
-
-import static android.app.ActivityThread.currentApplication;
-
-import static com.android.launcher3.BuildConfig.IS_DEBUG_DEVICE;
-import static com.android.launcher3.config.FeatureFlags.FlagState.DISABLED;
-import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import static java.util.Collections.unmodifiableList;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.provider.DeviceConfig;
-import android.provider.DeviceConfig.Properties;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.ConstantItem;
-import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.config.FeatureFlags.BooleanFlag;
-import com.android.launcher3.config.FeatureFlags.FlagState;
-import com.android.launcher3.config.FeatureFlags.IntFlag;
-import com.android.launcher3.util.ScreenOnTracker;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Helper class to create various flags for system build
- */
-public class FlagsFactory {
-
- private static final String TAG = "FlagsFactory";
-
- private static final FlagsFactory INSTANCE = new FlagsFactory();
- private static final boolean FLAG_AUTO_APPLY_ENABLED = true;
-
- private static final String FLAGS_PREF_NAME = "featureFlags";
- public static final String NAMESPACE_LAUNCHER = "launcher";
-
- private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
- private static final List<IntDebugFlag> sIntDebugFlags = new ArrayList<>();
- private static SharedPreferences sSharedPreferences;
-
- static final BooleanFlag TEAMFOOD_FLAG = getReleaseFlag(
- 0, "LAUNCHER_TEAMFOOD", DISABLED, "Enable this flag to opt-in all team food flags");
-
- private final Set<String> mKeySet = new HashSet<>();
- private boolean mRestartRequested = false;
-
- private FlagsFactory() {
- if (!FLAG_AUTO_APPLY_ENABLED) {
- return;
- }
- DeviceConfig.addOnPropertiesChangedListener(
- NAMESPACE_LAUNCHER, UI_HELPER_EXECUTOR, this::onPropertiesChanged);
- }
-
- static boolean getEnabledValue(FlagState flagState) {
- if (IS_DEBUG_DEVICE) {
- switch (flagState) {
- case ENABLED:
- return true;
- case TEAMFOOD:
- return TEAMFOOD_FLAG.get();
- default:
- return false;
- }
- } else {
- return flagState == ENABLED;
- }
- }
-
- /**
- * Creates a new debug flag. Debug flags always take their default value in release builds. On
- * dogfood builds, they can be manually turned on using the flag toggle UI.
- */
- public static BooleanFlag getDebugFlag(
- int bugId, String key, FlagState flagState, String description) {
- if (IS_DEBUG_DEVICE) {
- boolean defaultValue = getEnabledValue(flagState);
- boolean currentValue = getSharedPreferences().getBoolean(key, defaultValue);
- DebugFlag flag = new DebugFlag(key, description, flagState, currentValue);
- sDebugFlags.add(flag);
- return flag;
- } else {
- return new BooleanFlag(getEnabledValue(flagState));
- }
- }
-
- /**
- * Creates a new release flag. Release flags can be rolled out using server configurations and
- * also allow manual overrides on debug builds.
- */
- public static BooleanFlag getReleaseFlag(
- int bugId, String key, FlagState flagState, String description) {
- INSTANCE.mKeySet.add(key);
- boolean defaultValueInCode = getEnabledValue(flagState);
- boolean defaultValue = DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValueInCode);
- if (IS_DEBUG_DEVICE) {
- boolean currentValue = getSharedPreferences().getBoolean(key, defaultValue);
- DebugFlag flag = new DeviceFlag(key, description,
- (defaultValue == defaultValueInCode) ? flagState
- : defaultValue ? ENABLED : DISABLED, currentValue, defaultValueInCode);
- sDebugFlags.add(flag);
- return flag;
- } else {
- return new BooleanFlag(defaultValue);
- }
- }
-
- /**
- * Creates a new integer flag. Integer flags are always release flags
- */
- public static IntFlag getIntFlag(
- int bugId, String key, int defaultValueInCode, String description) {
- return getIntFlag(bugId, key, defaultValueInCode, description, null);
- }
-
- /**
- * Creates a new integer flag.
- *
- * @param launcherPrefFlag Set launcherPrefFlag to non-null if you want
- * to modify the int flag in Launcher Developer Options and IntDebugFlag
- * will be backed up by LauncherPrefs. Modified int value will be saved
- * in LauncherPrefs.
- */
- public static IntFlag getIntFlag(
- int bugId, String key, int defaultValueInCode, String description,
- @Nullable ConstantItem<Integer> launcherPrefFlag) {
- INSTANCE.mKeySet.add(key);
- int defaultValue = DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode);
- if (IS_DEBUG_DEVICE) {
- int currentValue;
- if (launcherPrefFlag == null) {
- currentValue = defaultValue;
- } else {
- currentValue = LauncherPrefs.get(currentApplication()).get(launcherPrefFlag);
- }
- IntDebugFlag flag = new IntDebugFlag(key, currentValue, defaultValueInCode,
- launcherPrefFlag);
- sIntDebugFlags.add(flag);
- return flag;
- } else {
- return new IntFlag(defaultValue);
- }
- }
-
- static List<DebugFlag> getDebugFlags() {
- if (!IS_DEBUG_DEVICE) {
- return Collections.emptyList();
- }
- synchronized (sDebugFlags) {
- return new ArrayList<>(sDebugFlags);
- }
- }
-
- static List<IntDebugFlag> getIntDebugFlags() {
- if (!IS_DEBUG_DEVICE) {
- return unmodifiableList(Collections.emptyList());
- }
- synchronized (sIntDebugFlags) {
- return unmodifiableList(sIntDebugFlags);
- }
- }
-
- /** Returns the SharedPreferences instance backing Debug FeatureFlags. */
- @NonNull
- static SharedPreferences getSharedPreferences() {
- if (sSharedPreferences == null) {
- sSharedPreferences = currentApplication()
- .createDeviceProtectedStorageContext()
- .getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
- }
- return sSharedPreferences;
- }
-
- /**
- * Dumps the current flags state to the print writer
- */
- public static void dump(PrintWriter pw) {
- if (!IS_DEBUG_DEVICE) {
- return;
- }
- pw.println("DeviceFlags:");
- pw.println(" BooleanFlags:");
- synchronized (sDebugFlags) {
- for (DebugFlag flag : sDebugFlags) {
- if (flag instanceof DeviceFlag) {
- pw.println((flag.currentValueModified() ? " ->" : " ") + flag);
- }
- }
- }
- pw.println(" IntFlags:");
- synchronized (sIntDebugFlags) {
- for (IntFlag flag : sIntDebugFlags) {
- pw.println(" " + flag);
- }
- }
- pw.println(" DebugFlags:");
- synchronized (sDebugFlags) {
- for (DebugFlag flag : sDebugFlags) {
- if (!(flag instanceof DeviceFlag)) {
- pw.println((flag.currentValueModified() ? " ->" : " ") + flag);
- }
- }
- }
- }
-
- private void onPropertiesChanged(Properties properties) {
- if (!Collections.disjoint(properties.getKeyset(), mKeySet)) {
- // Schedule a restart
- if (mRestartRequested) {
- return;
- }
- Log.e(TAG, "Flag changed, scheduling restart");
- mRestartRequested = true;
- ScreenOnTracker sot = ScreenOnTracker.INSTANCE.get(currentApplication());
- if (sot.isScreenOn()) {
- sot.addListener(this::onScreenOnChanged);
- } else {
- onScreenOnChanged(false);
- }
- }
- }
-
- private void onScreenOnChanged(boolean isOn) {
- if (mRestartRequested && !isOn) {
- Log.e(TAG, "Restart requested, killing process");
- System.exit(0);
- }
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java
deleted file mode 100644
index 1350aa8..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides.flags;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.ConstantItem;
-import com.android.launcher3.config.FeatureFlags.IntFlag;
-
-public class IntDebugFlag extends IntFlag {
- public final String key;
- private final int mDefaultValueInCode;
- @Nullable
- public final ConstantItem<Integer> launcherPrefFlag;
-
- public IntDebugFlag(String key, int currentValue, int defaultValueInCode,
- @Nullable ConstantItem<Integer> launcherPrefFlag) {
- super(currentValue);
- this.key = key;
- mDefaultValueInCode = defaultValueInCode;
- this.launcherPrefFlag = launcherPrefFlag;
- }
-
- @Override
- public String toString() {
- return key + ": mCurrentValue=" + get() + ", defaultValueInCode=" + mDefaultValueInCode;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
index faa900b..4e09f1f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
@@ -18,12 +18,10 @@
import android.content.Context;
import android.content.SharedPreferences;
-import androidx.preference.PreferenceDataStore;
-
import com.android.launcher3.LauncherPrefs;
import com.android.systemui.shared.plugins.PluginEnabler;
-public class PluginEnablerImpl extends PreferenceDataStore implements PluginEnabler {
+public class PluginEnablerImpl implements PluginEnabler {
private static final String PREFIX_PLUGIN_ENABLED = "PLUGIN_ENABLED_";
@@ -44,12 +42,12 @@
}
private void setState(ComponentName component, boolean enabled) {
- putBoolean(pluginEnabledKey(component), enabled);
+ mSharedPrefs.edit().putBoolean(pluginEnabledKey(component), enabled).apply();
}
@Override
public boolean isEnabled(ComponentName component) {
- return getBoolean(pluginEnabledKey(component), true);
+ return mSharedPrefs.getBoolean(pluginEnabledKey(component), true);
}
@Override
@@ -57,17 +55,7 @@
return isEnabled(componentName) ? ENABLED : DISABLED_MANUALLY;
}
- @Override
- public void putBoolean(String key, boolean value) {
- mSharedPrefs.edit().putBoolean(key, value).apply();
- }
-
- @Override
- public boolean getBoolean(String key, boolean defValue) {
- return mSharedPrefs.getBoolean(key, defValue);
- }
-
- static String pluginEnabledKey(ComponentName cn) {
+ private static String pluginEnabledKey(ComponentName cn) {
return PREFIX_PLUGIN_ENABLED + cn.flattenToString();
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapperImpl.java
similarity index 71%
rename from quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
rename to quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapperImpl.java
index 7f78713..74572c4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapperImpl.java
@@ -1,15 +1,17 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
+ * 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.
+ * 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.plugins;
@@ -24,11 +26,10 @@
import android.content.Intent;
import android.content.pm.ResolveInfo;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.BuildConfig;
+import com.android.launcher3.util.PluginManagerWrapper;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginActionManager;
import com.android.systemui.shared.plugins.PluginInstance;
import com.android.systemui.shared.plugins.PluginManagerImpl;
@@ -41,35 +42,30 @@
import java.util.List;
import java.util.Set;
-public class PluginManagerWrapper {
-
- public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
- new MainThreadInitializedObject<>(PluginManagerWrapper::new);
-
- public static final String PLUGIN_CHANGED = PluginManager.PLUGIN_CHANGED;
+public class PluginManagerWrapperImpl extends PluginManagerWrapper {
private static final UncaughtExceptionPreHandlerManager UNCAUGHT_EXCEPTION_PRE_HANDLER_MANAGER =
new UncaughtExceptionPreHandlerManager();
private final Context mContext;
- private final PluginManager mPluginManager;
+ private final PluginManagerImpl mPluginManager;
private final PluginEnablerImpl mPluginEnabler;
- private PluginManagerWrapper(Context c) {
+ public PluginManagerWrapperImpl(Context c) {
mContext = c;
mPluginEnabler = new PluginEnablerImpl(c);
List<String> privilegedPlugins = Collections.emptyList();
PluginInstance.Factory instanceFactory = new PluginInstance.Factory(
getClass().getClassLoader(), new PluginInstance.InstanceFactory<>(),
new PluginInstance.VersionCheckerImpl(), privilegedPlugins,
- Utilities.IS_DEBUG_DEVICE);
+ BuildConfig.IS_DEBUG_DEVICE);
PluginActionManager.Factory instanceManagerFactory = new PluginActionManager.Factory(
c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR,
c.getSystemService(NotificationManager.class), mPluginEnabler,
privilegedPlugins, instanceFactory);
mPluginManager = new PluginManagerImpl(c, instanceManagerFactory,
- Utilities.IS_DEBUG_DEVICE,
+ BuildConfig.IS_DEBUG_DEVICE,
UNCAUGHT_EXCEPTION_PRE_HANDLER_MANAGER, mPluginEnabler,
new PluginPrefs(c), privilegedPlugins);
}
@@ -78,18 +74,13 @@
return mPluginEnabler;
}
- /** */
- public <T extends Plugin> void addPluginListener(
- PluginListener<T> listener, Class<T> pluginClass) {
- addPluginListener(listener, pluginClass, false);
- }
-
- /** */
+ @Override
public <T extends Plugin> void addPluginListener(
PluginListener<T> listener, Class<T> pluginClass, boolean allowMultiple) {
mPluginManager.addPluginListener(listener, pluginClass, allowMultiple);
}
+ @Override
public void removePluginListener(PluginListener<? extends Plugin> listener) {
mPluginManager.removePluginListener(listener);
}
@@ -98,17 +89,12 @@
return new PluginPrefs(mContext).getPluginList();
}
- /**
- * Returns the string key used to store plugin enabled/disabled setting
- */
- public static String pluginEnabledKey(ComponentName cn) {
- return PluginEnablerImpl.pluginEnabledKey(cn);
+ /** Notifies that a plugin state has changed */
+ public void notifyChange(Intent intent) {
+ mPluginManager.onReceive(mContext, intent);
}
- public static boolean hasPlugins(Context context) {
- return PluginPrefs.hasPlugins(context);
- }
-
+ @Override
public void dump(PrintWriter pw) {
final List<ComponentName> enabledPlugins = new ArrayList<>();
final List<ComponentName> disabledPlugins = new ArrayList<>();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 577eba6..fa80dc2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides.states;
import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import android.content.Context;
@@ -28,8 +29,11 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.util.BaseDepthController;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import java.util.concurrent.TimeUnit;
+
/**
* Definition for AllApps state
*/
@@ -37,6 +41,8 @@
private static final int STATE_FLAGS =
FLAG_WORKSPACE_INACCESSIBLE | FLAG_CLOSE_POPUPS | FLAG_HOTSEAT_INACCESSIBLE;
+ private static final long BACK_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
+
public AllAppsState(int id) {
super(id, LAUNCHER_STATE_ALLAPPS, STATE_FLAGS);
@@ -51,14 +57,36 @@
}
@Override
- public void onBackPressed(Launcher launcher) {
+ public void onBackStarted(Launcher launcher) {
+ // Because the back gesture can take longer time depending on when user release the finger,
+ // we pass BACK_CUJ_TIMEOUT_MS as timeout to the jank monitor.
InteractionJankMonitorWrapper.begin(launcher.getAppsView(),
- Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
- super.onBackPressed(launcher);
+ Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK, BACK_CUJ_TIMEOUT_MS);
+ super.onBackStarted(launcher);
}
@Override
- protected void onBackPressCompleted(boolean success) {
+ public void onBackInvoked(Launcher launcher) {
+ // In predictive back swipe, onBackInvoked() will be called after onBackStarted().
+ // In 3 button mode, onBackStarted() is not called but onBackInvoked() will be called.
+ // Thus In onBackInvoked(), we should only begin instrumenting if we didn't call
+ // onBackStarted() to start instrumenting CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK.
+ if (!InteractionJankMonitorWrapper.isInstrumenting(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK)) {
+ InteractionJankMonitorWrapper.begin(
+ launcher.getAppsView(), Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
+ }
+ super.onBackInvoked(launcher);
+ }
+
+ /** Called when predictive back swipe is cancelled. */
+ @Override
+ public void onBackCancelled(Launcher launcher) {
+ super.onBackCancelled(launcher);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
+ }
+
+ @Override
+ protected void onBackAnimationCompleted(boolean success) {
if (success) {
// Animation was successful.
InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
@@ -74,6 +102,11 @@
}
@Override
+ public int getTitle() {
+ return R.string.all_apps_label;
+ }
+
+ @Override
public float getVerticalProgress(Launcher launcher) {
return 0f;
}
@@ -105,8 +138,14 @@
return context.getDeviceProfile().bottomSheetDepth;
} else {
// The scrim fades in at approximately 50% of the swipe gesture.
- // This means that the depth should be greater than 1, in order to fully zoom out.
- return 2f;
+ if (enableScalingRevealHomeAnimation()) {
+ // This means that the depth should be twice of what we want, in order to fully zoom
+ // out during the visible portion of the animation.
+ return BaseDepthController.DEPTH_60_PERCENT;
+ } else {
+ // This means that the depth should be greater than 1, in order to fully zoom out.
+ return 2f;
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index d11a08b..2625646 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -15,17 +15,17 @@
*/
package com.android.launcher3.uioverrides.states;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import android.content.Context;
import android.graphics.Color;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.quickstep.util.BaseDepthController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -34,7 +34,7 @@
*/
public class BackgroundAppState extends OverviewState {
- private static final int STATE_FLAGS = FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI
+ private static final int STATE_FLAGS = FLAG_DISABLE_RESTORE | FLAG_RECENTS_VIEW_VISIBLE
| FLAG_WORKSPACE_INACCESSIBLE | FLAG_NON_INTERACTIVE | FLAG_CLOSE_POPUPS;
public BackgroundAppState(int id) {
@@ -62,7 +62,7 @@
@Override
public float[] getOverviewScaleAndOffset(Launcher launcher) {
- return getOverviewScaleAndOffsetForBackgroundState(launcher);
+ return getOverviewScaleAndOffsetForBackgroundState(launcher.getOverviewPanel());
}
@Override
@@ -90,13 +90,14 @@
@Override
protected float getDepthUnchecked(Context context) {
- if (isDesktopModeSupported()) {
- if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
- // Don't blur the background while freeform tasks are visible
- return 0;
- }
+ if (Launcher.getLauncher(context).areDesktopTasksVisible()) {
+ // Don't blur the background while desktop tasks are visible
+ return BaseDepthController.DEPTH_0_PERCENT;
+ } else if (enableScalingRevealHomeAnimation()) {
+ return BaseDepthController.DEPTH_70_PERCENT;
+ } else {
+ return 1f;
}
- return 1;
}
@Override
@@ -123,9 +124,7 @@
}
public static float[] getOverviewScaleAndOffsetForBackgroundState(
- BaseDraggingActivity activity) {
- return new float[] {
- ((RecentsView) activity.getOverviewPanel()).getMaxScaleForFullScreen(),
- NO_OFFSET};
+ RecentsView recentsView) {
+ return new float[] {recentsView.getMaxScaleForFullScreen(), NO_OFFSET};
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 856b519..932d241 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.graphics.Rect;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
@@ -33,7 +32,7 @@
public class OverviewModalTaskState extends OverviewState {
private static final int STATE_FLAGS =
- FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_WORKSPACE_INACCESSIBLE;
+ FLAG_DISABLE_RESTORE | FLAG_RECENTS_VIEW_VISIBLE | FLAG_WORKSPACE_INACCESSIBLE;
public OverviewModalTaskState(int id) {
super(id, LAUNCHER_STATE_OVERVIEW, STATE_FLAGS);
@@ -51,7 +50,7 @@
@Override
public float[] getOverviewScaleAndOffset(Launcher launcher) {
- return getOverviewScaleAndOffsetForModalState(launcher);
+ return getOverviewScaleAndOffsetForModalState(launcher.getOverviewPanel());
}
@Override
@@ -60,7 +59,7 @@
}
@Override
- public void onBackPressed(Launcher launcher) {
+ public void onBackInvoked(Launcher launcher) {
launcher.getStateManager().goToState(LauncherState.OVERVIEW);
}
@@ -72,8 +71,7 @@
return super.isTaskbarStashed(launcher);
}
- public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
- RecentsView recentsView = activity.<RecentsView>getOverviewPanel();
+ public static float[] getOverviewScaleAndOffsetForModalState(RecentsView recentsView) {
Rect taskSize = recentsView.getSelectedTaskBounds();
Rect modalTaskSize = new Rect();
recentsView.getModalTaskSize(modalTaskSize);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 7650235..6822f1b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides.states;
import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import static com.android.wm.shell.Flags.enableSplitContextual;
@@ -29,6 +30,7 @@
import com.android.launcher3.R;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Themes;
+import com.android.quickstep.util.BaseDepthController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -45,7 +47,7 @@
protected static final Rect sTempRect = new Rect();
private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
- | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_WORKSPACE_INACCESSIBLE
+ | FLAG_DISABLE_RESTORE | FLAG_RECENTS_VIEW_VISIBLE | FLAG_WORKSPACE_INACCESSIBLE
| FLAG_CLOSE_POPUPS;
public OverviewState(int id) {
@@ -180,18 +182,29 @@
return launcher.getString(R.string.accessibility_recent_apps);
}
+ @Override
+ public int getTitle() {
+ return R.string.accessibility_recent_apps;
+ }
+
public static float getDefaultSwipeHeight(Launcher launcher) {
return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
}
@Override
protected float getDepthUnchecked(Context context) {
- //TODO revert when b/178661709 is fixed
- return SystemProperties.getBoolean("ro.launcher.depth.overview", true) ? 1 : 0;
+ // TODO(178661709): revert to always scaled
+ if (enableScalingRevealHomeAnimation()) {
+ return SystemProperties.getBoolean("ro.launcher.depth.overview", true)
+ ? BaseDepthController.DEPTH_70_PERCENT
+ : BaseDepthController.DEPTH_0_PERCENT;
+ } else {
+ return SystemProperties.getBoolean("ro.launcher.depth.overview", true) ? 1 : 0;
+ }
}
@Override
- public void onBackPressed(Launcher launcher) {
+ public void onBackInvoked(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
TaskView taskView = recentsView.getRunningTaskView();
if (taskView != null) {
@@ -201,7 +214,7 @@
recentsView.snapToPage(recentsView.indexOfChild(taskView));
}
} else {
- super.onBackPressed(launcher);
+ super.onBackInvoked(launcher);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index ba44d6a..dfad409 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -16,7 +16,6 @@
package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import android.graphics.Color;
@@ -46,11 +45,9 @@
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
- if (isDesktopModeSupported()) {
- if (launcher.areFreeformTasksVisible()) {
- // No scrim while freeform tasks are visible
- return Color.TRANSPARENT;
- }
+ if (launcher.areDesktopTasksVisible()) {
+ // No scrim while desktop tasks are visible
+ return Color.TRANSPARENT;
}
DeviceProfile dp = launcher.getDeviceProfile();
if (dp.isTaskbarPresentInApps) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 8c92c7d..0368f3a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -37,7 +37,6 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
@@ -59,6 +58,7 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.Workspace;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AllAppsSwipeController;
@@ -94,7 +94,7 @@
@Override
public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
StateAnimationConfig config) {
- RecentsView overview = mActivity.getOverviewPanel();
+ RecentsView overview = mContainer.getOverviewPanel();
if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) {
overview.switchToScreenshot(() ->
overview.finishRecentsAnimation(true /* toRecents */, null));
@@ -108,7 +108,8 @@
// We sync the scrim fade with the taskbar animation duration to avoid any flickers for
// taskbar icons disappearing before hotseat icons show up.
- float scrimUpperBoundFromSplit = TASKBAR_TO_HOME_DURATION / (float) config.duration;
+ float scrimUpperBoundFromSplit =
+ QuickstepTransitionManager.getTaskbarToHomeDuration() / (float) config.duration;
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
config.setInterpolator(ANIM_SCRIM_FADE,
fromState == OVERVIEW_SPLIT_SELECT
@@ -117,7 +118,7 @@
config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATE);
config.setInterpolator(ANIM_WORKSPACE_FADE, ACCELERATE);
- if (DisplayController.getNavigationMode(mActivity).hasGestures
+ if (DisplayController.getNavigationMode(mContainer).hasGestures
&& overview.getTaskViewCount() > 0) {
// Overview is going offscreen, so keep it at its current scale and opacity.
config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
@@ -135,8 +136,9 @@
config.duration = Math.max(config.duration, scrollDuration);
// Sync scroll so that it ends before or at the same time as the taskbar animation.
- if (mActivity.getDeviceProfile().isTaskbarPresent) {
- config.duration = Math.min(config.duration, TASKBAR_TO_HOME_DURATION);
+ if (mContainer.getDeviceProfile().isTaskbarPresent) {
+ config.duration = Math.min(
+ config.duration, QuickstepTransitionManager.getTaskbarToHomeDuration());
}
overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
} else {
@@ -145,7 +147,7 @@
config.setInterpolator(ANIM_OVERVIEW_FADE, DECELERATE_1_7);
}
- Workspace<?> workspace = mActivity.getWorkspace();
+ Workspace<?> workspace = mContainer.getWorkspace();
// Start from a higher workspace scale, but only if we're invisible so we don't jump.
boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
if (isWorkspaceVisible) {
@@ -158,7 +160,7 @@
workspace.setScaleX(WORKSPACE_PREPARE_SCALE);
workspace.setScaleY(WORKSPACE_PREPARE_SCALE);
}
- Hotseat hotseat = mActivity.getHotseat();
+ Hotseat hotseat = mContainer.getHotseat();
boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
if (!isHotseatVisible) {
hotseat.setScaleX(WORKSPACE_PREPARE_SCALE);
@@ -166,7 +168,7 @@
}
} else if ((fromState == NORMAL || fromState == HINT_STATE
|| fromState == HINT_STATE_TWO_BUTTON) && toState == OVERVIEW) {
- if (DisplayController.getNavigationMode(mActivity).hasGestures) {
+ if (DisplayController.getNavigationMode(mContainer).hasGestures) {
config.setInterpolator(ANIM_WORKSPACE_SCALE,
fromState == NORMAL ? ACCELERATE : OVERSHOOT_1_2);
config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCELERATE);
@@ -199,18 +201,18 @@
} else if (fromState == HINT_STATE && toState == NORMAL) {
config.setInterpolator(ANIM_DEPTH, DECELERATE_3);
if (mHintToNormalDuration == -1) {
- ValueAnimator va = getWorkspaceSpringScaleAnimator(mActivity,
- mActivity.getWorkspace(),
- toState.getWorkspaceScaleAndTranslation(mActivity).scale);
+ ValueAnimator va = getWorkspaceSpringScaleAnimator(mContainer,
+ mContainer.getWorkspace(),
+ toState.getWorkspaceScaleAndTranslation(mContainer).scale);
mHintToNormalDuration = (int) va.getDuration();
}
config.duration = Math.max(config.duration, mHintToNormalDuration);
} else if (fromState == ALL_APPS && toState == NORMAL) {
- AllAppsSwipeController.applyAllAppsToNormalConfig(mActivity, config);
+ AllAppsSwipeController.applyAllAppsToNormalConfig(mContainer, config);
} else if (fromState == NORMAL && toState == ALL_APPS) {
- AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mActivity, config);
+ AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mContainer, config);
} else if (fromState == OVERVIEW && toState == OVERVIEW_SPLIT_SELECT) {
- SplitAnimationTimings timings = mActivity.getDeviceProfile().isTablet
+ SplitAnimationTimings timings = mContainer.getDeviceProfile().isTablet
? SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT
: SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT;
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index e8b5081..11e0ed5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -19,7 +19,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
-import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
+import static com.android.launcher3.LauncherAnimUtils.newSingleUseCancelListener;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
@@ -118,7 +118,7 @@
if (!cameFromNavBar) {
return false;
}
- if (mStartState.overviewUi || mStartState == ALL_APPS) {
+ if (mStartState.isRecentsViewVisible || mStartState == ALL_APPS) {
return true;
}
int typeToClose = TYPE_ALL & ~TYPE_ALL_APPS_EDU;
@@ -145,7 +145,7 @@
private void initCurrentAnimation() {
long accuracy = (long) (getShiftRange() * 2);
final PendingAnimation builder = new PendingAnimation(accuracy);
- if (mStartState.overviewUi) {
+ if (mStartState.isRecentsViewVisible) {
RecentsView recentsView = mLauncher.getOverviewPanel();
AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
builder);
@@ -165,7 +165,7 @@
topView.addHintCloseAnim(mPullbackDistance, PULLBACK_INTERPOLATOR, builder);
}
mCurrentAnimation = builder.createPlaybackController();
- mCurrentAnimation.getTarget().addListener(newCancelListener(this::clearState));
+ mCurrentAnimation.getTarget().addListener(newSingleUseCancelListener(this::clearState));
}
private void clearState() {
@@ -194,7 +194,7 @@
RecentsView recentsView = mLauncher.getOverviewPanel();
recentsView.switchToScreenshot(null,
() -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
- if (mStartState.overviewUi) {
+ if (mStartState.isRecentsViewVisible) {
Runnable onReachedHome = () -> {
StateManager.StateListener<LauncherState> listener =
new StateManager.StateListener<>() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 26e994f..3325009 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -18,7 +18,7 @@
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
-import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
+import static com.android.launcher3.LauncherAnimUtils.newSingleUseCancelListener;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -226,7 +226,7 @@
return;
}
mNormalToHintOverviewScrimAnimator = null;
- mCurrentAnimation.getTarget().addListener(newCancelListener(() ->
+ mCurrentAnimation.getTarget().addListener(newSingleUseCancelListener(() ->
mLauncher.getStateManager().goToState(OVERVIEW, true, forSuccessCallback(() -> {
mOverviewResistYAnim = AnimatorControllerWithResistance
.createRecentsResistanceFromOverviewAnim(mLauncher, null)
@@ -249,7 +249,7 @@
}
private boolean handlingOverviewAnim() {
- int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
+ long stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
return mDidTouchStartInNavBar && mStartState == NORMAL
&& (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 3a1c42d..ab277b6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -49,7 +49,6 @@
import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
@@ -85,7 +84,6 @@
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.WorkspaceRevealAnim;
-import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -109,9 +107,9 @@
private final float mMaxYProgress;
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
- private final LauncherRecentsView mRecentsView;
+ private final RecentsView mRecentsView;
protected final AnimatorListener mClearStateOnCancelListener =
- newCancelListener(this::clearState);
+ newCancelListener(this::clearState, /* isSingleUse = */ false);
private boolean mNoIntercept;
private LauncherState mStartState;
@@ -174,14 +172,10 @@
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
return false;
}
- int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
+ long stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return false;
}
- if (isDesktopModeSupported()) {
- // TODO(b/268075592): add support for quickswitch to/from desktop
- return false;
- }
if (isTrackpadMultiFingerSwipe(ev)) {
return isTrackpadFourFingerSwipe(ev);
}
@@ -254,7 +248,7 @@
TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, fromState.showTaskThumbnailSplash() ? 1f : 0);
mRecentsView.setContentAlpha(1);
mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
- mLauncher.getActionsView().getVisibilityAlpha().setValue(
+ mLauncher.getActionsView().getVisibilityAlpha().updateValue(
(fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f);
mRecentsView.setTaskIconScaledDown(true);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index ff142fe..93e4fbd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -30,7 +30,6 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
@@ -39,12 +38,12 @@
import android.view.MotionEvent;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.SystemUiProxy;
@@ -59,11 +58,12 @@
protected final RecentsView mOverviewPanel;
- public QuickSwitchTouchController(Launcher launcher) {
+ public QuickSwitchTouchController(QuickstepLauncher launcher) {
this(launcher, SingleAxisSwipeDetector.HORIZONTAL);
}
- protected QuickSwitchTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
+ protected QuickSwitchTouchController(QuickstepLauncher l,
+ SingleAxisSwipeDetector.Direction dir) {
super(l, dir);
mOverviewPanel = l.getOverviewPanel();
}
@@ -79,16 +79,12 @@
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
return false;
}
- if (isDesktopModeSupported()) {
- // TODO(b/268075592): add support for quickswitch to/from desktop
- return false;
- }
return true;
}
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
- int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
+ long stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return NORMAL;
}
@@ -155,7 +151,7 @@
int sysuiFlags = 0;
TaskView tv = mOverviewPanel.getTaskViewAt(0);
if (tv != null) {
- sysuiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
+ sysuiFlags = tv.getFirstThumbnailViewDeprecated().getSysUiStatusNavFlags();
}
mLauncher.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, sysuiFlags);
} else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index e9f2d4f..4bc3c16 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -21,6 +21,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
import android.os.VibrationEffect;
import android.view.MotionEvent;
import android.view.View;
@@ -28,7 +29,6 @@
import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -44,12 +44,13 @@
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.VibrationConstants;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
/**
* Touch controller for handling task view card swipes
*/
-public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
+public abstract class TaskViewTouchController<CONTAINER extends Context & RecentsViewContainer>
extends AnimatorListenerAdapter implements TouchController,
SingleAxisSwipeDetector.Listener {
@@ -63,7 +64,7 @@
public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK =
VibrationConstants.EFFECT_TEXTURE_TICK;
- protected final T mActivity;
+ protected final CONTAINER mContainer;
private final SingleAxisSwipeDetector mDetector;
private final RecentsView mRecentsView;
private final int[] mTempCords = new int[2];
@@ -87,13 +88,13 @@
private boolean mIsDismissHapticRunning = false;
- public TaskViewTouchController(T activity) {
- mActivity = activity;
- mRecentsView = activity.getOverviewPanel();
- mIsRtl = Utilities.isRtl(activity.getResources());
+ public TaskViewTouchController(CONTAINER container) {
+ mContainer = container;
+ mRecentsView = container.getOverviewPanel();
+ mIsRtl = Utilities.isRtl(container.getResources());
SingleAxisSwipeDetector.Direction dir =
mRecentsView.getPagedOrientationHandler().getUpDownSwipeDirection();
- mDetector = new SingleAxisSwipeDetector(activity, this, dir);
+ mDetector = new SingleAxisSwipeDetector(container, this, dir);
}
private boolean canInterceptTouch(MotionEvent ev) {
@@ -113,7 +114,7 @@
return true;
}
if (AbstractFloatingView.getTopOpenViewWithType(
- mActivity, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
+ mContainer, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
return false;
}
return isRecentsInteractive();
@@ -159,7 +160,7 @@
for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
TaskView view = mRecentsView.getTaskViewAt(i);
- if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
+ if (mRecentsView.isTaskViewVisible(view) && mContainer.getDragLayer()
.isEventOverView(view, ev)) {
// Disable swiping up and down if the task overlay is modal.
if (isRecentsModal()) {
@@ -179,7 +180,7 @@
// - It's the focused task if in grid view
// - The task is snapped
mAllowGoingDown = i == mRecentsView.getCurrentPage()
- && DisplayController.getNavigationMode(mActivity).hasGestures
+ && DisplayController.getNavigationMode(mContainer).hasGestures
&& (!mRecentsView.showAsGrid() || mTaskBeingDragged.isFocusedTask())
&& mRecentsView.isTaskInExpectedScrollPosition(i);
@@ -228,7 +229,7 @@
RecentsPagedOrientationHandler orientationHandler =
mRecentsView.getPagedOrientationHandler();
mCurrentAnimationIsGoingUp = goingUp;
- BaseDragLayer dl = mActivity.getDragLayer();
+ BaseDragLayer dl = mContainer.getDragLayer();
final int secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl);
long maxDuration = 2 * secondaryLayerDimension;
int verticalFactor = orientationHandler.getTaskDragDisplacementFactor(mIsRtl);
@@ -251,7 +252,7 @@
mTaskBeingDragged, maxDuration, currentInterpolator);
// Since the thumbnail is what is filling the screen, based the end displacement on it.
- View thumbnailView = mTaskBeingDragged.getThumbnail();
+ View thumbnailView = mTaskBeingDragged.getFirstThumbnailViewDeprecated();
mTempCords[1] = orientationHandler.getSecondaryDimension(thumbnailView);
dl.getDescendantCoordRelativeToSelf(thumbnailView, mTempCords);
mEndDisplacement = secondaryLayerDimension - mTempCords[1];
@@ -372,10 +373,10 @@
MIN_TASK_DISMISS_ANIMATION_DURATION, MAX_TASK_DISMISS_ANIMATION_DURATION);
mCurrentAnimation.setEndAction(this::clearState);
- mCurrentAnimation.startWithVelocity(mActivity, goingToEnd, Math.abs(velocity),
+ mCurrentAnimation.startWithVelocity(mContainer, goingToEnd, Math.abs(velocity),
mEndDisplacement, animationDuration);
if (goingUp && goingToEnd && !mIsDismissHapticRunning) {
- VibratorWrapper.INSTANCE.get(mActivity).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE,
+ VibratorWrapper.INSTANCE.get(mContainer).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE,
TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE, TASK_DISMISS_VIBRATION_FALLBACK);
mIsDismissHapticRunning = true;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
index 8f9c014..b70cabe 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
@@ -15,13 +15,13 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
public class TransposedQuickSwitchTouchController extends QuickSwitchTouchController {
- public TransposedQuickSwitchTouchController(Launcher launcher) {
+ public TransposedQuickSwitchTouchController(QuickstepLauncher launcher) {
super(launcher, SingleAxisSwipeDetector.VERTICAL);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
index 9f2c1d4..31c9b3e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
@@ -27,10 +27,10 @@
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.AllAppsEduView;
@@ -51,7 +51,7 @@
private int mContinuousTouchCount = 0;
- public TwoButtonNavbarTouchController(Launcher l) {
+ public TwoButtonNavbarTouchController(QuickstepLauncher l) {
super(l, l.getDeviceProfile().isVerticalBarLayout()
? SingleAxisSwipeDetector.HORIZONTAL : SingleAxisSwipeDetector.VERTICAL);
mIsTransposed = l.getDeviceProfile().isVerticalBarLayout();
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 4752225..1acafab 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -22,16 +22,17 @@
import static android.widget.Toast.LENGTH_SHORT;
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
-import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.app.animation.Interpolators.DECELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
import static com.android.launcher3.BaseActivity.EVENT_STARTED;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
-import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.PagedView.INVALID_PAGE;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
@@ -59,7 +60,6 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.INVALID_VELOCITY_ON_SWIPE_UP;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -101,7 +101,6 @@
import com.android.internal.util.LatencyTracker;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -109,9 +108,7 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
-import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarThresholdUtils;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -138,9 +135,11 @@
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -149,12 +148,16 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.window.flags.Flags;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
+import kotlin.Unit;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
@@ -163,7 +166,7 @@
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
-public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
+public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
Q extends RecentsView, S extends BaseState<S>>
extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
RecentsAnimationCallbacks.RecentsAnimationListener {
@@ -174,7 +177,7 @@
// Fraction of the scroll and transform animation in which the current task fades out
private static final float KQS_TASK_FADE_ANIMATION_FRACTION = 0.4f;
- protected final BaseActivityInterface<S, T> mActivityInterface;
+ protected final BaseContainerInterface<S, T> mContainerInterface;
protected final InputConsumerProxy mInputConsumerProxy;
protected final ActivityInitListener mActivityInitListener;
// Callbacks to be made once the recents animation starts
@@ -185,7 +188,7 @@
protected @Nullable RecentsAnimationController mRecentsAnimationController;
protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
- protected @Nullable T mActivity;
+ protected @Nullable T mContainer;
protected @Nullable Q mRecentsView;
protected Runnable mGestureEndCallback;
protected MultiStateCallback mStateCallback;
@@ -195,7 +198,7 @@
private final Runnable mLauncherOnDestroyCallback = () -> {
ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
mRecentsView = null;
- mActivity = null;
+ mContainer = null;
mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
};
@@ -350,8 +353,9 @@
long touchTimeMs, boolean continuingLastGesture,
InputConsumerController inputConsumer) {
super(context, deviceState, gestureState);
- mActivityInterface = gestureState.getActivityInterface();
- mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
+ mContainerInterface = gestureState.getContainerInterface();
+ mActivityInitListener =
+ mContainerInterface.createActivityInitListener(this::onActivityInit);
mInputConsumerProxy =
new InputConsumerProxy(context, /* rotationSupplier = */ () -> {
if (mRecentsView == null) {
@@ -361,7 +365,7 @@
}, inputConsumer, /* onTouchDownCallback = */ () -> {
endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
endLauncherTransitionController();
- }, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
+ }, new InputProxyHandlerFactory(mContainerInterface, mGestureState));
mTaskAnimationManager = taskAnimationManager;
mTouchTimeMs = touchTimeMs;
mContinuingLastGesture = continuingLastGesture;
@@ -378,8 +382,8 @@
initStateCallbacks();
mIsTransientTaskbar = mDp.isTaskbarPresent
- && DisplayController.isTransientTaskbar(mActivity);
- TaskbarUIController controller = mActivityInterface.getTaskbarController();
+ && DisplayController.isTransientTaskbar(context);
+ TaskbarUIController controller = mContainerInterface.getTaskbarController();
mTaskbarAlreadyOpen = controller != null && !controller.isTaskbarStashed();
mIsTaskbarAllAppsOpen = controller != null && controller.isTaskbarAllAppsOpen();
mTaskbarAppWindowThreshold =
@@ -476,16 +480,16 @@
return false;
}
- T createdActivity = mActivityInterface.getCreatedActivity();
- if (createdActivity != null) {
- initTransitionEndpoints(createdActivity.getDeviceProfile());
+ T createdContainer = (T) mContainerInterface.getCreatedContainer();
+ if (createdContainer != null) {
+ initTransitionEndpoints(createdContainer.getDeviceProfile());
}
- final T activity = mActivityInterface.getCreatedActivity();
- if (mActivity == activity) {
+ final T container = (T) mContainerInterface.getCreatedContainer();
+ if (mContainer == container) {
return true;
}
- if (mActivity != null) {
+ if (mContainer != null) {
if (mStateCallback.hasStates(STATE_GESTURE_COMPLETED)) {
// If the activity has restarted between setting the page scroll settling callback
// and actually receiving the callback, just mark the gesture completed
@@ -500,23 +504,23 @@
mStateCallback.setState(oldState);
}
mWasLauncherAlreadyVisible = alreadyOnHome;
- mActivity = activity;
+ mContainer = container;
// Override the visibility of the activity until the gesture actually starts and we swipe
// up, or until we transition home and the home animation is composed
if (alreadyOnHome) {
- mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ mContainer.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
} else {
- mActivity.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ mContainer.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
}
- mRecentsView = activity.getOverviewPanel();
+ mRecentsView = container.getOverviewPanel();
mRecentsView.setOnPageTransitionEndCallback(null);
mStateCallback.setState(STATE_LAUNCHER_PRESENT);
if (alreadyOnHome) {
onLauncherStart();
} else {
- activity.addEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
+ container.addEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
}
// Set up a entire animation lifecycle callback to notify the current recents view when
@@ -541,8 +545,8 @@
setupRecentsViewUi();
mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
- mActivity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
- mActivity.addEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
+ mContainer.runOnBindToTouchInteractionService(this::onLauncherBindToService);
+ mContainer.addEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
return true;
}
@@ -554,8 +558,8 @@
}
private void onLauncherStart() {
- final T activity = mActivityInterface.getCreatedActivity();
- if (activity == null || mActivity != activity) {
+ final T container = (T) mContainerInterface.getCreatedContainer();
+ if (container == null || mContainer != container) {
return;
}
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
@@ -571,7 +575,7 @@
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
if (mGestureState.getEndTarget() != HOME) {
Runnable initAnimFactory = () -> {
- mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
+ mAnimationFactory = mContainerInterface.prepareRecentsUI(mDeviceState,
mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
maybeUpdateRecentsAttachedState(false /* animate */);
if (mGestureState.getEndTarget() != null) {
@@ -588,14 +592,14 @@
initAnimFactory.run();
}
}
- AbstractFloatingView.closeAllOpenViewsExcept(activity, mWasLauncherAlreadyVisible,
+ AbstractFloatingView.closeAllOpenViewsExcept(container, mWasLauncherAlreadyVisible,
AbstractFloatingView.TYPE_LISTENER);
if (mWasLauncherAlreadyVisible) {
mStateCallback.setState(STATE_LAUNCHER_DRAWN);
} else {
SafeCloseable traceToken = TraceHelper.INSTANCE.beginAsyncSection("WTS-init");
- View dragLayer = activity.getDragLayer();
+ View dragLayer = container.getDragLayer();
dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
boolean mHandled = false;
@@ -609,7 +613,7 @@
traceToken.close();
dragLayer.post(() ->
dragLayer.getViewTreeObserver().removeOnDrawListener(this));
- if (activity != mActivity) {
+ if (container != mContainer) {
return;
}
@@ -618,7 +622,7 @@
});
}
- activity.getRootView().setOnApplyWindowInsetsListener(this);
+ container.getRootView().setOnApplyWindowInsetsListener(this);
mStateCallback.setState(STATE_LAUNCHER_STARTED);
}
@@ -634,21 +638,21 @@
// For the duration of the gesture, in cases where an activity is launched while the
// activity is not yet resumed, finish the animation to ensure we get resumed
- mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
+ mGestureState.getContainerInterface().setOnDeferredActivityLaunchCallback(
mOnDeferredActivityLaunch);
mGestureState.runOnceAtState(STATE_END_TARGET_SET,
() -> {
mDeviceState.getRotationTouchHelper()
.onEndTargetCalculated(mGestureState.getEndTarget(),
- mActivityInterface);
+ mContainerInterface);
});
notifyGestureStarted();
}
private void onDeferredActivityLaunch() {
- mActivityInterface.switchRunningTaskViewToScreenshot(
+ mContainerInterface.switchRunningTaskViewToScreenshot(
null, () -> {
mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
});
@@ -709,7 +713,7 @@
public void onMotionPauseDetected() {
mHasMotionEverBeenPaused = true;
maybeUpdateRecentsAttachedState(true/* animate */, true/* moveRunningTask */);
- Optional.ofNullable(mActivityInterface.getTaskbarController())
+ Optional.ofNullable(mContainerInterface.getTaskbarController())
.ifPresent(TaskbarUIController::startTranslationSpring);
if (!mIsInAllAppsRegion) {
performHapticFeedback();
@@ -815,7 +819,7 @@
*/
private void setIsInAllAppsRegion(boolean isInAllAppsRegion) {
if (mIsInAllAppsRegion == isInAllAppsRegion
- || !mActivityInterface.allowAllAppsFromOverview()) {
+ || !mContainerInterface.allowAllAppsFromOverview()) {
return;
}
mIsInAllAppsRegion = isInAllAppsRegion;
@@ -824,8 +828,9 @@
VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
maybeUpdateRecentsAttachedState(true);
- if (mActivity != null) {
- mActivity.getAppsView().getSearchUiManager().prepareToFocusEditText(mIsInAllAppsRegion);
+ if (mContainer != null) {
+ mContainer.getAppsView().getSearchUiManager()
+ .prepareToFocusEditText(mIsInAllAppsRegion);
}
// Draw active task below Launcher so that All Apps can appear over it.
@@ -838,7 +843,7 @@
if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
return;
}
- initTransitionEndpoints(mActivity.getDeviceProfile());
+ initTransitionEndpoints(mContainer.getDeviceProfile());
mAnimationFactory.createActivityInterface(mTransitionDragLength);
}
@@ -849,7 +854,7 @@
*/
private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
return mGestureState.getEndTarget() != HOME
- && !mHasEndedLauncherTransition && mActivity != null;
+ && !mHasEndedLauncherTransition && mContainer != null;
}
@Override
@@ -893,7 +898,7 @@
@UiThread
@Override
public void onCurrentShiftUpdated() {
- float threshold = LauncherPrefs.get(mContext).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;
+ float threshold = DeviceConfigWrapper.get().getAllAppsOverviewThreshold() / 100f;
setIsInAllAppsRegion(mCurrentShift.value >= threshold);
updateSysUiFlags(mCurrentShift.value);
applyScrollAndTransform();
@@ -921,7 +926,7 @@
TaskView runningTask = mRecentsView.getRunningTaskView();
TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
int centermostTaskFlags = centermostTask == null ? 0
- : centermostTask.getThumbnail().getSysUiStatusNavFlags();
+ : centermostTask.getFirstThumbnailViewDeprecated().getSysUiStatusNavFlags();
boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
boolean quickswitchThresholdPassed = centermostTask != runningTask;
@@ -933,11 +938,11 @@
// needs to be canceled
mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed);
- if (mActivity == null) return;
+ if (mContainer == null) return;
if (swipeUpThresholdPassed) {
- mActivity.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
+ mContainer.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
} else {
- mActivity.getSystemUiController().updateUiState(
+ mContainer.getSystemUiController().updateUiState(
UI_STATE_FULLSCREEN_TASK, centermostTaskFlags);
}
}
@@ -947,7 +952,7 @@
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
super.onRecentsAnimationStart(controller, targets);
- if (isDesktopModeSupported() && targets.hasDesktopTasks()) {
+ if (targets.hasDesktopTasks()) {
mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
} else {
int untrimmedAppCount = mRemoteTargetHandles.length;
@@ -965,7 +970,7 @@
// Only initialize the device profile, if it has not been initialized before, as in some
// configurations targets.homeContentInsets may not be correct.
- if (mActivity == null) {
+ if (mContainer == null) {
RemoteAnimationTarget primaryTaskTarget = targets.apps[0];
// orientation state is independent of which remote target handle we use since both
// should be pointing to the same one. Just choose index 0 for now since that works for
@@ -974,7 +979,7 @@
.getOrientationState();
DeviceProfile dp = orientationState.getLauncherDeviceProfile();
if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) {
- Rect overviewStackBounds = mActivityInterface
+ Rect overviewStackBounds = mContainerInterface
.getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget);
dp = dp.getMultiWindowProfile(mContext,
new WindowBounds(overviewStackBounds, targets.homeContentInsets));
@@ -1002,7 +1007,7 @@
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "cancelRecentsAnimation",
/* gestureEvent= */ CANCEL_RECENTS_ANIMATION);
- mActivityInitListener.unregister();
+ mActivityInitListener.unregister("AbsSwipeUpHandler.onRecentsAnimationCanceled");
// Cache the recents animation controller so we can defer its cleanup to after having
// properly cleaned up the screenshot without accidentally using it.
mDeferredCleanupRecentsAnimationController = mRecentsAnimationController;
@@ -1017,7 +1022,7 @@
@UiThread
public void onGestureStarted(boolean isLikelyToStartNewTask) {
- mActivityInterface.closeOverlay();
+ mContainerInterface.closeOverlay();
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
if (mRecentsView != null) {
@@ -1077,11 +1082,11 @@
*/
@UiThread
private void notifyGestureStarted() {
- final T curActivity = mActivity;
+ final T curActivity = mContainer;
if (curActivity != null) {
// Once the gesture starts, we can no longer transition home through the button, so
// reset the force override of the activity visibility
- mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ mContainer.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
}
}
@@ -1150,7 +1155,7 @@
maybeUpdateRecentsAttachedState(false);
final GestureEndTarget endTarget = mGestureState.getEndTarget();
// Wait until the given View (if supplied) draws before resuming the last task.
- View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
+ View postResumeLastTask = mContainerInterface.onSettledOnEndTarget(endTarget);
if (endTarget != NEW_TASK) {
InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
@@ -1170,14 +1175,6 @@
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify the SysUI to use fade-in animation when entering PiP
SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
- if (isDesktopModeSupported()) {
- // Notify the SysUI to stash desktop apps if they are visible
- DesktopVisibilityController desktopVisibilityController =
- mActivityInterface.getDesktopVisibilityController();
- if (desktopVisibilityController != null) {
- desktopVisibilityController.onHomeActionTriggered();
- }
- }
break;
case RECENTS:
mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -1197,6 +1194,10 @@
setDividerShown(true);
break;
}
+ if (mContainerInterface.getTaskbarController() != null) {
+ // Resets this value as the gesture is now complete.
+ mContainerInterface.getTaskbarController().setUserIsGoingHome(false);
+ }
ActiveGestureLog.INSTANCE.addLog(
new ActiveGestureLog.CompoundString("onSettledOnEndTarget ")
.append(endTarget.name()),
@@ -1255,11 +1256,20 @@
return LAST_TASK;
}
- if (isDesktopModeSupported() && endTarget == NEW_TASK) {
- // TODO(b/268075592): add support for quickswitch to/from desktop
- return LAST_TASK;
- }
+ TaskView nextPageTaskView = mRecentsView != null
+ ? mRecentsView.getNextPageTaskView() : null;
+ TaskView currentPageTaskView = mRecentsView != null
+ ? mRecentsView.getCurrentPageTaskView() : null;
+ if (Flags.enableDesktopWindowingMode()
+ && !(Flags.enableDesktopWindowingWallpaperActivity()
+ && Flags.enableDesktopWindowingQuickSwitch())) {
+ if ((nextPageTaskView instanceof DesktopTaskView
+ || currentPageTaskView instanceof DesktopTaskView)
+ && endTarget == NEW_TASK) {
+ return LAST_TASK;
+ }
+ }
return endTarget;
}
@@ -1336,6 +1346,13 @@
mGestureState.setEndTarget(endTarget, false /* isAtomic */);
mAnimationFactory.setEndTarget(endTarget);
+ if (enableScalingRevealHomeAnimation()
+ && mIsTransientTaskbar
+ && mContainerInterface.getTaskbarController() != null) {
+ mContainerInterface.getTaskbarController()
+ .setUserIsGoingHome(endTarget == GestureState.GestureEndTarget.HOME);
+ }
+
float endShift = endTarget == ALL_APPS ? mDragLengthFactor
: endTarget.isLauncher ? 1 : 0;
final float startShift;
@@ -1358,7 +1375,7 @@
}
}
Interpolator interpolator;
- S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
+ S state = mContainerInterface.stateFromGestureEndTarget(endTarget);
if (isKeyboardTaskFocusPending()) {
interpolator = EMPHASIZED;
} else if (state.displayOverviewTasksAsGrid(mDp)) {
@@ -1373,7 +1390,7 @@
mInputConsumerProxy.enable();
}
if (endTarget == HOME) {
- duration = mActivity != null && mActivity.getDeviceProfile().isTaskbarPresent
+ duration = mContainer != null && mContainer.getDeviceProfile().isTaskbarPresent
? StaggeredWorkspaceAnim.DURATION_TASKBAR_MS
: StaggeredWorkspaceAnim.DURATION_MS;
// Early detach the nav bar once the endTarget is determined as HOME
@@ -1416,12 +1433,27 @@
mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
setClampScrollOffset(false);
};
- if (mRecentsView != null) {
- ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent
- .SET_ON_PAGE_TRANSITION_END_CALLBACK);
- mRecentsView.setOnPageTransitionEndCallback(onPageTransitionEnd);
+
+ if (Flags.enableDesktopWindowingMode()
+ && !(Flags.enableDesktopWindowingWallpaperActivity()
+ && Flags.enableDesktopWindowingQuickSwitch())) {
+ if (mRecentsView != null && (mRecentsView.getCurrentPageTaskView() != null
+ && !(mRecentsView.getCurrentPageTaskView() instanceof DesktopTaskView))) {
+ ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent
+ .SET_ON_PAGE_TRANSITION_END_CALLBACK);
+ mRecentsView.setOnPageTransitionEndCallback(onPageTransitionEnd);
+ } else {
+ onPageTransitionEnd.run();
+ }
} else {
- onPageTransitionEnd.run();
+ if (mRecentsView != null) {
+ ActiveGestureLog.INSTANCE.trackEvent(
+ ActiveGestureErrorDetector
+ .GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK);
+ mRecentsView.setOnPageTransitionEndCallback(onPageTransitionEnd);
+ } else {
+ onPageTransitionEnd.run();
+ }
}
animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
@@ -1449,14 +1481,15 @@
default:
event = IGNORE;
}
- StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
+ StatsLogger logger = StatsLogManager.newInstance(
+ mContainer != null ? mContainer.asContext() : mContext).logger()
.withSrcState(LAUNCHER_STATE_BACKGROUND)
.withDstState(endTarget.containerType)
.withInputType(mGestureState.isTrackpadGesture()
? SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TRACKPAD
: SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TOUCH);
if (targetTask != null) {
- logger.withItemInfo(targetTask.getItemInfo());
+ logger.withItemInfo(targetTask.getFirstItemInfo());
}
int pageIndex = endTarget == LAST_TASK || mRecentsView == null
@@ -1475,8 +1508,12 @@
}
protected abstract HomeAnimationFactory createHomeAnimationFactory(
- ArrayList<IBinder> launchCookies, long duration, boolean isTargetTranslucent,
- boolean appCanEnterPip, RemoteAnimationTarget runningTaskTarget);
+ List<IBinder> launchCookies,
+ long duration,
+ boolean isTargetTranslucent,
+ boolean appCanEnterPip,
+ RemoteAnimationTarget runningTaskTarget,
+ @Nullable TaskView targetTaskView);
private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
@Override
@@ -1508,14 +1545,14 @@
if (mGestureState.getEndTarget().isLauncher) {
// This is also called when the launcher is resumed, in order to clear the pending
// widgets that have yet to be configured.
- if (mActivity != null) {
- DragView.removeAllViews(mActivity);
+ if (mContainer != null) {
+ DragView.removeAllViews(mContainer);
}
TaskStackChangeListeners.getInstance().registerTaskStackListener(
mActivityRestartListener);
- mParallelRunningAnim = mActivityInterface.getParallelAnimationToLauncher(
+ mParallelRunningAnim = mContainerInterface.getParallelAnimationToLauncher(
mGestureState.getEndTarget(), duration,
mTaskAnimationManager.getCurrentCallbacks());
if (mParallelRunningAnim != null) {
@@ -1550,9 +1587,16 @@
&& runningTaskTarget.allowEnterPip
&& runningTaskTarget.taskInfo.pictureInPictureParams != null
&& runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
- HomeAnimationFactory homeAnimFactory =
- createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
- runningTaskTarget);
+ HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(
+ cookies,
+ duration,
+ isTranslucent,
+ appCanEnterPip,
+ runningTaskTarget,
+ !enableAdditionalHomeAnimations()
+ || mRecentsView == null
+ || mRecentsView.getCurrentPage() == mRecentsView.getRunningTaskIndex()
+ ? null : mRecentsView.getCurrentPageTaskView());
SwipePipToHomeAnimator swipePipToHomeAnimator = !mIsSwipeForSplit && appCanEnterPip
? createWindowAnimationToPip(homeAnimFactory, runningTaskTarget, start)
: null;
@@ -1582,7 +1626,8 @@
mSwipePipToHomeAnimator.getComponentName(),
mSwipePipToHomeAnimator.getDestinationBounds(),
mSwipePipToHomeAnimator.getContentOverlay(),
- mSwipePipToHomeAnimator.getAppBounds());
+ mSwipePipToHomeAnimator.getAppBounds(),
+ mSwipePipToHomeAnimator.getSourceRectHint());
windowAnim = mSwipePipToHomeAnimators;
} else {
@@ -1613,7 +1658,7 @@
if (windowAnimation == null) {
continue;
}
- DeviceProfile dp = mActivity == null ? null : mActivity.getDeviceProfile();
+ DeviceProfile dp = mContainer == null ? null : mContainer.getDeviceProfile();
windowAnimation.start(mContext, dp, velocityPxPerMs);
mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation);
}
@@ -1866,7 +1911,7 @@
}
// Make sure recents is in its final state
maybeUpdateRecentsAttachedState(false);
- mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
+ mContainerInterface.onSwipeUpToHomeComplete(mDeviceState);
}
});
if (mRecentsAnimationTargets != null) {
@@ -1940,8 +1985,8 @@
private void reset() {
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
- if (mActivity != null) {
- mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
+ if (mContainer != null) {
+ mContainer.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
}
}
@@ -1958,14 +2003,14 @@
// Cleanup when switching handlers
mInputConsumerProxy.unregisterOnTouchDownCallback();
- mActivityInitListener.unregister();
+ mActivityInitListener.unregister("AbsSwipeUpHandler.cancelCurrentAnimation");
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mActivityRestartListener);
mTaskSnapshotCache.clear();
}
private void invalidateHandler() {
- if (!mActivityInterface.isInLiveTileMode() || mGestureState.getEndTarget() != RECENTS) {
+ if (!mContainerInterface.isInLiveTileMode() || mGestureState.getEndTarget() != RECENTS) {
mInputConsumerProxy.destroy();
mTaskAnimationManager.setLiveTileCleanUpHandler(null);
}
@@ -1976,7 +2021,7 @@
mGestureEndCallback.run();
}
- mActivityInitListener.unregister();
+ mActivityInitListener.unregister("AbsSwipeUpHandler.invalidateHandler");
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mActivityRestartListener);
mTaskSnapshotCache.clear();
@@ -2012,11 +2057,11 @@
* continued quick switch gesture, which cancels the previous handler but doesn't invalidate it.
*/
private void resetLauncherListeners() {
- if (mActivity != null) {
- mActivity.removeEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
- mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
+ if (mContainer != null) {
+ mContainer.removeEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
+ mContainer.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
- mActivity.getRootView().setOnApplyWindowInsetsListener(null);
+ mContainer.getRootView().setOnApplyWindowInsetsListener(null);
}
if (mRecentsView != null) {
mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
@@ -2025,11 +2070,11 @@
private void resetStateForAnimationCancel() {
boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
- mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
+ mContainerInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
// Leave the pending invisible flag, as it may be used by wallpaper open animation.
- if (mActivity != null) {
- mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+ if (mContainer != null) {
+ mContainer.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
}
}
@@ -2232,6 +2277,16 @@
mRecentsAnimationController, mRecentsAnimationTargets);
});
+ if (Flags.enableDesktopWindowingMode()
+ && !(Flags.enableDesktopWindowingWallpaperActivity()
+ && Flags.enableDesktopWindowingQuickSwitch())) {
+ if (mRecentsView.getNextPageTaskView() instanceof DesktopTaskView
+ || mRecentsView.getCurrentPageTaskView() instanceof DesktopTaskView) {
+ mRecentsViewScrollLinked = false;
+ return;
+ }
+ }
+
// Disable scrolling in RecentsView for trackpad 3-finger swipe up gesture.
if (!mGestureState.isThreeFingerTrackpadGesture()) {
mRecentsViewScrollLinked = true;
@@ -2260,15 +2315,15 @@
int[] taskIds = nextTask.getTaskIds();
ActiveGestureLog.CompoundString nextTaskLog = new ActiveGestureLog.CompoundString(
"Launching task: ");
- for (TaskIdAttributeContainer c : nextTask.getTaskIdAttributeContainers()) {
- if (c == null) {
+ for (TaskContainer container : nextTask.getTaskContainers()) {
+ if (container == null) {
continue;
}
nextTaskLog
.append("[id: ")
- .append(c.getTask().key.id)
+ .append(container.getTask().key.id)
.append(", pkg: ")
- .append(c.getTask().key.getPackageName())
+ .append(container.getTask().key.getPackageName())
.append("] | ");
}
mGestureState.updateLastStartedTaskIds(taskIds);
@@ -2286,14 +2341,15 @@
onRestartPreviouslyAppearedTask();
}
} else {
- mActivityInterface.onLaunchTaskFailed();
+ mContainerInterface.onLaunchTaskFailed();
if (mRecentsAnimationController != null) {
mRecentsAnimationController.finish(true /* toRecents */, null);
}
}
+ return Unit.INSTANCE;
}, true /* freezeTaskList */);
} else {
- mActivityInterface.onLaunchTaskFailed();
+ mContainerInterface.onLaunchTaskFailed();
Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
if (mRecentsAnimationController != null) {
mRecentsAnimationController.finish(true /* toRecents */, null);
@@ -2380,17 +2436,18 @@
RemoteAnimationTarget taskTarget = taskTargetOptional.get();
TaskView taskView = mRecentsView == null
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
- if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
+ if (taskView == null
+ || !taskView.getFirstThumbnailViewDeprecated().shouldShowSplashView()) {
ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state");
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
- if (mActivity == null) {
+ if (mContainer == null) {
ActiveGestureLog.INSTANCE.addLog("Activity destroyed");
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
- animateSplashScreenExit(mActivity, appearedTaskTargets, taskTarget.leash);
+ animateSplashScreenExit(mContainer, appearedTaskTargets, taskTarget.leash);
}
private void animateSplashScreenExit(
@@ -2471,11 +2528,11 @@
/**
* Registers a callback to run when the activity is ready.
*/
- public void initWhenReady() {
+ public void initWhenReady(String reasonString) {
// Preload the plan
RecentsModel.INSTANCE.get(mContext).getTasks(null);
- mActivityInitListener.register();
+ mActivityInitListener.register(reasonString);
}
private boolean shouldFadeOutTargetsForKeyboardQuickSwitch(
@@ -2505,7 +2562,7 @@
transaction.setAlpha(app.leash, 1f - fadeProgress);
transaction.setPosition(app.leash,
/* x= */ app.startBounds.left
- + (mActivity.getDeviceProfile().overviewPageSpacing
+ + (mContainer.getDeviceProfile().overviewPageSpacing
* (mRecentsView.isRtl() ? fadeProgress : -fadeProgress)),
/* y= */ 0f);
transaction.setScale(app.leash, 1f, 1f);
@@ -2556,7 +2613,7 @@
// Scaling of RecentsView during quick switch based on amount of recents scroll
private float getScaleProgressDueToScroll() {
- if (mActivity == null || !mActivity.getDeviceProfile().isTablet || mRecentsView == null
+ if (mContainer == null || !mContainer.getDeviceProfile().isTablet || mRecentsView == null
|| !shouldLinkRecentsViewScroll()) {
return 0;
}
diff --git a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
index fd2ed3a..6fd68d5 100644
--- a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
+++ b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
@@ -81,6 +81,7 @@
}
fun onDestroy() {
+ isActionRegistered = false
context
.getSystemService(AccessibilityManager::class.java)
?.unregisterSystemAction(
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 2341e4c..00cd60b 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -18,14 +18,11 @@
import static com.android.app.animation.Interpolators.ACCELERATE_2;
import static com.android.app.animation.Interpolators.INSTANT;
import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
-import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -34,23 +31,12 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.view.Gravity;
import android.view.MotionEvent;
-import android.view.RemoteAnimationTarget;
-import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
-import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statehandlers.DepthController;
@@ -58,29 +44,23 @@
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarUIController;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.views.ScrimView;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.HashMap;
import java.util.Optional;
import java.util.function.Consumer;
-import java.util.function.Predicate;
/**
* Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
*/
public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
- ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>> {
-
- public final boolean rotationSupportedByActivity;
-
+ ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE> & RecentsViewContainer> extends
+ BaseContainerInterface<STATE_TYPE, ACTIVITY_TYPE> {
private final STATE_TYPE mBackgroundState;
private STATE_TYPE mTargetState;
@@ -101,7 +81,7 @@
*/
public void onTransitionCancelled(boolean activityVisible,
@Nullable GestureState.GestureEndTarget endTarget) {
- ACTIVITY_TYPE activity = getCreatedActivity();
+ ACTIVITY_TYPE activity = getCreatedContainer();
if (activity == null) {
return;
}
@@ -109,67 +89,37 @@
if (endTarget != null) {
// We were on our way to this state when we got canceled, end there instead.
startState = stateFromGestureEndTarget(endTarget);
- if (isDesktopModeSupported()) {
- DesktopVisibilityController controller = getDesktopVisibilityController();
- if (controller != null && controller.areFreeformTasksVisible()
- && endTarget == LAST_TASK) {
- // When we are cancelling the transition and going back to last task, move to
- // rest state instead when desktop tasks are visible.
- // If a fullscreen task is visible, launcher goes to normal state when the
- // activity is stopped. This does not happen when freeform tasks are visible
- // on top of launcher. Force the launcher state to rest state here.
- startState = activity.getStateManager().getRestState();
- // Do not animate the transition
- activityVisible = false;
- }
+ DesktopVisibilityController controller = getDesktopVisibilityController();
+ if (controller != null && controller.areDesktopTasksVisible()
+ && endTarget == LAST_TASK) {
+ // When we are cancelling the transition and going back to last task, move to
+ // rest state instead when desktop tasks are visible.
+ // If a fullscreen task is visible, launcher goes to normal state when the
+ // activity is stopped. This does not happen when desktop tasks are visible
+ // on top of launcher. Force the launcher state to rest state here.
+ startState = activity.getStateManager().getRestState();
+ // Do not animate the transition
+ activityVisible = false;
}
}
activity.getStateManager().goToState(startState, activityVisible);
}
- public abstract int getSwipeUpDestinationAndLength(
- DeviceProfile dp, Context context, Rect outRect,
- RecentsPagedOrientationHandler orientationHandler);
-
- /** Called when the animation to home has fully settled. */
- public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {}
-
- public abstract void onAssistantVisibilityChanged(float visibility);
-
- public abstract AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
- boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback);
-
- public abstract ActivityInitListener createActivityInitListener(
- Predicate<Boolean> onInitListener);
-
- /**
- * Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
- */
- public void setOnDeferredActivityLaunchCallback(Runnable r) {}
-
@Nullable
- public abstract ACTIVITY_TYPE getCreatedActivity();
+ public abstract ACTIVITY_TYPE getCreatedContainer();
@Nullable
public DepthController getDepthController() {
return null;
}
- @Nullable
- public DesktopVisibilityController getDesktopVisibilityController() {
- return null;
- }
-
- @Nullable
- public abstract TaskbarUIController getTaskbarController();
-
public final boolean isResumed() {
- ACTIVITY_TYPE activity = getCreatedActivity();
+ ACTIVITY_TYPE activity = getCreatedContainer();
return activity != null && activity.hasBeenResumed();
}
public final boolean isStarted() {
- ACTIVITY_TYPE activity = getCreatedActivity();
+ ACTIVITY_TYPE activity = getCreatedContainer();
return activity != null && activity.isStarted();
}
@@ -180,14 +130,6 @@
@UiThread
public abstract boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener);
- public abstract Rect getOverviewWindowBounds(
- Rect homeBounds, RemoteAnimationTarget target);
-
- public abstract boolean allowMinimizeSplitScreen();
-
- /** @return whether to allow going to All Apps from Overview. */
- public abstract boolean allowAllAppsFromOverview();
-
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
TaskbarUIController controller = getTaskbarController();
boolean isEventOverBubbleBarStashHandle =
@@ -197,20 +139,6 @@
}
/**
- * @return Whether the gesture in progress should be cancelled.
- */
- public boolean shouldCancelCurrentGesture() {
- return false;
- }
-
- public abstract void onExitOverview(RotationTouchHelper deviceState,
- Runnable exitRunnable);
-
- public abstract boolean isInLiveTileMode();
-
- public abstract void onLaunchTaskFailed();
-
- /**
* Closes any overlays.
*/
public void closeOverlay() {
@@ -220,7 +148,7 @@
public void switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas,
Runnable runnable) {
- ACTIVITY_TYPE activity = getCreatedActivity();
+ ACTIVITY_TYPE activity = getCreatedContainer();
if (activity == null) {
return;
}
@@ -234,237 +162,9 @@
recentsView.switchToScreenshot(thumbnailDatas, runnable);
}
- /**
- * Calculates the taskView size for the provided device configuration.
- */
- public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
- PagedOrientationHandler orientedState) {
- if (dp.isTablet) {
- if (Flags.enableGridOnlyOverview()) {
- calculateGridTaskSize(context, dp, outRect, orientedState);
- } else {
- calculateFocusTaskSize(context, dp, outRect);
- }
- } else {
- Resources res = context.getResources();
- float maxScale = res.getFloat(R.dimen.overview_max_scale);
- int taskMargin = dp.overviewTaskMarginPx;
- calculateTaskSizeInternal(
- context,
- dp,
- dp.overviewTaskThumbnailTopMarginPx,
- dp.getOverviewActionsClaimedSpace(),
- res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
- maxScale,
- Gravity.CENTER,
- outRect);
- }
- }
-
- /**
- * Calculates the taskView size for carousel during app to overview animation on tablets.
- */
- public final void calculateCarouselTaskSize(Context context, DeviceProfile dp, Rect outRect,
- PagedOrientationHandler orientedState) {
- if (dp.isTablet && dp.isGestureMode) {
- Resources res = context.getResources();
- float minScale = res.getFloat(R.dimen.overview_carousel_min_scale);
- Rect gridRect = new Rect();
- calculateGridSize(dp, context, gridRect);
- calculateTaskSizeInternal(context, dp, gridRect, minScale, Gravity.CENTER | Gravity.TOP,
- outRect);
- } else {
- calculateTaskSize(context, dp, outRect, orientedState);
- }
- }
-
- private void calculateFocusTaskSize(Context context, DeviceProfile dp, Rect outRect) {
- Resources res = context.getResources();
- float maxScale = res.getFloat(R.dimen.overview_max_scale);
- Rect gridRect = new Rect();
- calculateGridSize(dp, context, gridRect);
- calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect);
- }
-
- private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove,
- int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity,
- Rect outRect) {
- Rect insets = dp.getInsets();
-
- Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
- potentialTaskRect.inset(insets.left, insets.top, insets.right, insets.bottom);
- potentialTaskRect.inset(
- minimumHorizontalPadding,
- claimedSpaceAbove,
- minimumHorizontalPadding,
- claimedSpaceBelow);
-
- calculateTaskSizeInternal(context, dp, potentialTaskRect, maxScale, gravity, outRect);
- }
-
- private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
- Rect potentialTaskRect, float targetScale, int gravity, Rect outRect) {
- PointF taskDimension = getTaskDimension(context, dp);
-
- float scale = Math.min(
- potentialTaskRect.width() / taskDimension.x,
- potentialTaskRect.height() / taskDimension.y);
- scale = Math.min(scale, targetScale);
- int outWidth = Math.round(scale * taskDimension.x);
- int outHeight = Math.round(scale * taskDimension.y);
-
- Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
- }
-
- private static PointF getTaskDimension(Context context, DeviceProfile dp) {
- PointF dimension = new PointF();
- getTaskDimension(context, dp, dimension);
- return dimension;
- }
-
- /**
- * Gets the dimension of the task in the current system state.
- */
- public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) {
- out.x = dp.widthPx;
- out.y = dp.heightPx;
- if (dp.isTablet && !DisplayController.isTransientTaskbar(context)) {
- out.y -= dp.taskbarHeight;
- }
- }
-
- /**
- * Calculates the overview grid size for the provided device configuration.
- */
- public final void calculateGridSize(DeviceProfile dp, Context context, Rect outRect) {
- Rect insets = dp.getInsets();
- int topMargin = dp.overviewTaskThumbnailTopMarginPx;
- int bottomMargin = dp.getOverviewActionsClaimedSpace();
- if (dp.isTaskbarPresent && Flags.enableGridOnlyOverview()) {
- topMargin += context.getResources().getDimensionPixelSize(
- R.dimen.overview_top_margin_grid_only);
- bottomMargin += context.getResources().getDimensionPixelSize(
- R.dimen.overview_bottom_margin_grid_only);
- }
- int sideMargin = dp.overviewGridSideMargin;
-
- outRect.set(0, 0, dp.widthPx, dp.heightPx);
- outRect.inset(Math.max(insets.left, sideMargin), insets.top + topMargin,
- Math.max(insets.right, sideMargin), Math.max(insets.bottom, bottomMargin));
- }
-
- /**
- * Calculates the overview grid non-focused task size for the provided device configuration.
- */
- public final void calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect,
- PagedOrientationHandler orientedState) {
- Resources res = context.getResources();
- Rect potentialTaskRect = new Rect();
- if (Flags.enableGridOnlyOverview()) {
- calculateGridSize(dp, context, potentialTaskRect);
- } else {
- calculateFocusTaskSize(context, dp, potentialTaskRect);
- }
-
- float rowHeight = (potentialTaskRect.height() + dp.overviewTaskThumbnailTopMarginPx
- - dp.overviewRowSpacing) / 2f;
-
- PointF taskDimension = getTaskDimension(context, dp);
- float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y;
- int outWidth = Math.round(scale * taskDimension.x);
- int outHeight = Math.round(scale * taskDimension.y);
-
- int gravity = Gravity.TOP;
- gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
- Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
- }
-
- /**
- * Calculates the task size for the desktop task
- */
- public final void calculateDesktopTaskSize(Context context, DeviceProfile dp, Rect outRect) {
- calculateFocusTaskSize(context, dp, outRect);
- }
-
- /**
- * Calculates the modal taskView size for the provided device configuration
- */
- public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect,
- PagedOrientationHandler orientedState) {
- calculateTaskSize(context, dp, outRect, orientedState);
- boolean isGridOnlyOverview = dp.isTablet && Flags.enableGridOnlyOverview();
- int claimedSpaceBelow = isGridOnlyOverview
- ? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight
- : (dp.heightPx - outRect.bottom - dp.getInsets().bottom);
- int minimumHorizontalPadding = 0;
- if (!isGridOnlyOverview) {
- float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale);
- minimumHorizontalPadding =
- Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2);
- }
- calculateTaskSizeInternal(
- context,
- dp,
- dp.overviewTaskMarginPx,
- claimedSpaceBelow,
- minimumHorizontalPadding,
- 1f /*maxScale*/,
- Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
- outRect);
- }
-
- /**
- * Called when the gesture ends and the animation starts towards the given target. Used to add
- * an optional additional animation with the same duration.
- */
- public @Nullable Animator getParallelAnimationToLauncher(
- GestureState.GestureEndTarget endTarget, long duration,
- RecentsAnimationCallbacks callbacks) {
- if (endTarget == RECENTS) {
- ACTIVITY_TYPE activity = getCreatedActivity();
- if (activity == null) {
- return null;
- }
- RecentsView recentsView = activity.getOverviewPanel();
- STATE_TYPE state = stateFromGestureEndTarget(endTarget);
- ScrimView scrimView = activity.getScrimView();
- ObjectAnimator anim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR,
- getOverviewScrimColorForState(activity, state));
- anim.setDuration(duration);
- anim.setInterpolator(recentsView == null || !recentsView.isKeyboardTaskFocusPending()
- ? LINEAR : INSTANT);
- return anim;
- }
- return null;
- }
-
- /**
- * Returns the color of the scrim behind overview when at rest in this state.
- * Return {@link Color#TRANSPARENT} for no scrim.
- */
- protected abstract int getOverviewScrimColorForState(ACTIVITY_TYPE activity, STATE_TYPE state);
-
- /**
- * Returns the expected STATE_TYPE from the provided GestureEndTarget.
- */
- public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
-
- /**
- * Called when the animation to the target has finished, but right before updating the state.
- * @return A View that needs to draw before ending the recents animation to LAST_TASK.
- * (This is a hack to ensure Taskbar draws its background first to avoid flickering.)
- */
- public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) {
- TaskbarUIController taskbarUIController = getTaskbarController();
- if (taskbarUIController != null) {
- taskbarUIController.setSystemGestureInProgress(false);
- return taskbarUIController.getRootView();
- }
- return null;
- }
protected void runOnInitBackgroundStateUI(Runnable callback) {
- ACTIVITY_TYPE activity = getCreatedActivity();
+ ACTIVITY_TYPE activity = getCreatedContainer();
if (activity != null && activity.getStateManager().getState() == mBackgroundState) {
callback.run();
onInitBackgroundStateUI();
@@ -515,7 +215,7 @@
DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback) {
mCallback = callback;
- mActivity = getCreatedActivity();
+ mActivity = getCreatedContainer();
mStartState = mActivity.getStateManager().getState();
}
diff --git a/quickstep/src/com/android/quickstep/BaseContainerInterface.java b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
new file mode 100644
index 0000000..b1d511c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
+import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.RemoteAnimationTarget;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
+import com.android.launcher3.R;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
+import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.taskbar.TaskbarUIController;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.views.ScrimView;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.util.HashMap;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+public abstract class BaseContainerInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
+ CONTAINER_TYPE extends RecentsViewContainer> {
+
+ public boolean rotationSupportedByActivity = false;
+
+ @Nullable
+ public abstract CONTAINER_TYPE getCreatedContainer();
+
+ public abstract boolean isInLiveTileMode();
+
+ public abstract void onAssistantVisibilityChanged(float assistantVisibility);
+
+ public abstract boolean allowMinimizeSplitScreen();
+
+ public abstract boolean isResumed();
+
+ public abstract boolean isStarted();
+ public abstract boolean deferStartingActivity(RecentsAnimationDeviceState deviceState,
+ MotionEvent ev);
+
+ /** @return whether to allow going to All Apps from Overview. */
+ public abstract boolean allowAllAppsFromOverview();
+
+ /**
+ * Returns the color of the scrim behind overview when at rest in this state.
+ * Return {@link Color#TRANSPARENT} for no scrim.
+ */
+ protected abstract int getOverviewScrimColorForState(CONTAINER_TYPE container,
+ STATE_TYPE state);
+
+ public abstract int getSwipeUpDestinationAndLength(
+ DeviceProfile dp, Context context, Rect outRect,
+ RecentsPagedOrientationHandler orientationHandler);
+
+ @Nullable
+ public abstract TaskbarUIController getTaskbarController();
+
+ public abstract BaseActivityInterface.AnimationFactory prepareRecentsUI(
+ RecentsAnimationDeviceState deviceState, boolean activityVisible,
+ Consumer<AnimatorControllerWithResistance> callback);
+
+ public abstract ActivityInitListener createActivityInitListener(
+ Predicate<Boolean> onInitListener);
+ /**
+ * Returns the expected STATE_TYPE from the provided GestureEndTarget.
+ */
+ public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
+
+ public abstract void switchRunningTaskViewToScreenshot(HashMap<Integer,
+ ThumbnailData> thumbnailDatas, Runnable runnable);
+
+ public abstract void closeOverlay();
+
+ public abstract Rect getOverviewWindowBounds(
+ Rect homeBounds, RemoteAnimationTarget target);
+
+ public abstract void onLaunchTaskFailed();
+
+ public abstract void onExitOverview(RotationTouchHelper deviceState,
+ Runnable exitRunnable);
+
+ /** Called when the animation to home has fully settled. */
+ public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {}
+
+ /**
+ * Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
+ */
+ public void setOnDeferredActivityLaunchCallback(Runnable r) {}
+ /**
+ * @return Whether the gesture in progress should be cancelled.
+ */
+ public boolean shouldCancelCurrentGesture() {
+ return false;
+ }
+
+ @Nullable
+ public DesktopVisibilityController getDesktopVisibilityController() {
+ return null;
+ }
+
+ /**
+ * Called when the gesture ends and the animation starts towards the given target. Used to add
+ * an optional additional animation with the same duration.
+ */
+ public @Nullable Animator getParallelAnimationToLauncher(
+ GestureState.GestureEndTarget endTarget, long duration,
+ RecentsAnimationCallbacks callbacks) {
+ if (endTarget == RECENTS) {
+ CONTAINER_TYPE container = getCreatedContainer();
+ if (container == null) {
+ return null;
+ }
+ RecentsView recentsView = container.getOverviewPanel();
+ STATE_TYPE state = stateFromGestureEndTarget(endTarget);
+ ScrimView scrimView = container.getScrimView();
+ ObjectAnimator anim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR,
+ getOverviewScrimColorForState(container, state));
+ anim.setDuration(duration);
+ anim.setInterpolator(recentsView == null || !recentsView.isKeyboardTaskFocusPending()
+ ? LINEAR : INSTANT);
+ return anim;
+ }
+ return null;
+ }
+
+ /**
+ * Called when the animation to the target has finished, but right before updating the state.
+ * @return A View that needs to draw before ending the recents animation to LAST_TASK.
+ * (This is a hack to ensure Taskbar draws its background first to avoid flickering.)
+ */
+ public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) {
+ TaskbarUIController taskbarUIController = getTaskbarController();
+ if (taskbarUIController != null) {
+ taskbarUIController.setSystemGestureInProgress(false);
+ return taskbarUIController.getRootView();
+ }
+ return null;
+ }
+
+ /**
+ * Called when the current gesture transition is cancelled.
+ * @param activityVisible Whether the user can see the changes we make here, so try to animate.
+ * @param endTarget If the gesture ended before we got cancelled, where we were headed.
+ */
+ public void onTransitionCancelled(boolean activityVisible,
+ @Nullable GestureState.GestureEndTarget endTarget) {
+ RecentsViewContainer container = getCreatedContainer();
+ if (container == null) {
+ return;
+ }
+ RecentsView recentsView = container.getOverviewPanel();
+ BaseState startState = recentsView.getStateManager().getRestState();
+ if (endTarget != null) {
+ // We were on our way to this state when we got canceled, end there instead.
+ startState = stateFromGestureEndTarget(endTarget);
+ DesktopVisibilityController controller = getDesktopVisibilityController();
+ if (controller != null && controller.areDesktopTasksVisible()
+ && endTarget == LAST_TASK) {
+ // When we are cancelling the transition and going back to last task, move to
+ // rest state instead when desktop tasks are visible.
+ // If a fullscreen task is visible, launcher goes to normal state when the
+ // activity is stopped. This does not happen when desktop tasks are visible
+ // on top of launcher. Force the launcher state to rest state here.
+ startState = recentsView.getStateManager().getRestState();
+ // Do not animate the transition
+ activityVisible = false;
+ }
+ }
+ recentsView.getStateManager().goToState(startState, activityVisible);
+ }
+
+ public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
+ RecentsPagedOrientationHandler orientationHandler) {
+ if (dp.isTablet) {
+ if (Flags.enableGridOnlyOverview()) {
+ calculateGridTaskSize(context, dp, outRect, orientationHandler);
+ } else {
+ calculateFocusTaskSize(context, dp, outRect);
+ }
+ } else {
+ Resources res = context.getResources();
+ float maxScale = res.getFloat(R.dimen.overview_max_scale);
+ int taskMargin = dp.overviewTaskMarginPx;
+ // In fake orientation, OverviewActions is hidden and we only leave a margin there.
+ int overviewActionsClaimedSpace = orientationHandler.isLayoutNaturalToLauncher()
+ ? dp.getOverviewActionsClaimedSpace() : dp.overviewActionsTopMarginPx;
+ calculateTaskSizeInternal(
+ context,
+ dp,
+ dp.overviewTaskThumbnailTopMarginPx,
+ overviewActionsClaimedSpace,
+ res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
+ maxScale,
+ Gravity.CENTER,
+ outRect,
+ orientationHandler);
+ }
+ }
+
+ /**
+ * Calculates the taskView size for carousel during app to overview animation on tablets.
+ */
+ public final void calculateCarouselTaskSize(Context context, DeviceProfile dp, Rect outRect,
+ RecentsPagedOrientationHandler orientationHandler) {
+ if (dp.isTablet && dp.isGestureMode) {
+ Resources res = context.getResources();
+ float minScale = res.getFloat(R.dimen.overview_carousel_min_scale);
+ Rect gridRect = new Rect();
+ calculateGridSize(dp, context, gridRect);
+ calculateTaskSizeInternal(context, dp, gridRect, minScale, Gravity.CENTER | Gravity.TOP,
+ outRect);
+ } else {
+ calculateTaskSize(context, dp, outRect, orientationHandler);
+ }
+ }
+
+ private void calculateFocusTaskSize(Context context, DeviceProfile dp, Rect outRect) {
+ Resources res = context.getResources();
+ float maxScale = res.getFloat(R.dimen.overview_max_scale);
+ Rect gridRect = new Rect();
+ calculateGridSize(dp, context, gridRect);
+ calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect);
+ }
+
+ private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove,
+ int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity,
+ Rect outRect, RecentsPagedOrientationHandler orientationHandler) {
+ Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
+
+ Rect insets;
+ if (orientationHandler.isLayoutNaturalToLauncher()) {
+ insets = dp.getInsets();
+ } else {
+ Rect portraitInsets = dp.getInsets();
+ DisplayController displayController = DisplayController.INSTANCE.get(context);
+ Rect deviceRotationInsets = displayController.getInfo().getCurrentBounds().get(
+ orientationHandler.getRotation()).insets;
+ // Obtain the landscape/seascape insets, and rotate it to portrait perspective.
+ orientationHandler.rotateInsets(deviceRotationInsets, outRect);
+ // Then combine with portrait's insets to leave space for status bar/nav bar in
+ // either orientations.
+ outRect.set(
+ Math.max(outRect.left, portraitInsets.left),
+ Math.max(outRect.top, portraitInsets.top),
+ Math.max(outRect.right, portraitInsets.right),
+ Math.max(outRect.bottom, portraitInsets.bottom)
+ );
+ insets = outRect;
+ }
+ potentialTaskRect.inset(insets);
+
+ outRect.set(
+ minimumHorizontalPadding,
+ claimedSpaceAbove,
+ minimumHorizontalPadding,
+ claimedSpaceBelow);
+ // Rotate the paddings to portrait perspective,
+ orientationHandler.rotateInsets(outRect, outRect);
+ potentialTaskRect.inset(outRect);
+
+ calculateTaskSizeInternal(context, dp, potentialTaskRect, maxScale, gravity, outRect);
+ }
+
+ private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
+ Rect potentialTaskRect, float targetScale, int gravity, Rect outRect) {
+ PointF taskDimension = getTaskDimension(context, dp);
+
+ float scale = Math.min(
+ potentialTaskRect.width() / taskDimension.x,
+ potentialTaskRect.height() / taskDimension.y);
+ scale = Math.min(scale, targetScale);
+ int outWidth = Math.round(scale * taskDimension.x);
+ int outHeight = Math.round(scale * taskDimension.y);
+
+ Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
+ }
+
+ private static PointF getTaskDimension(Context context, DeviceProfile dp) {
+ PointF dimension = new PointF();
+ getTaskDimension(context, dp, dimension);
+ return dimension;
+ }
+
+ /**
+ * Gets the dimension of the task in the current system state.
+ */
+ public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) {
+ out.x = dp.widthPx;
+ out.y = dp.heightPx;
+ if (dp.isTablet && !DisplayController.isTransientTaskbar(context)) {
+ out.y -= dp.taskbarHeight;
+ }
+ }
+
+ /**
+ * Calculates the overview grid size for the provided device configuration.
+ */
+ public final void calculateGridSize(DeviceProfile dp, Context context, Rect outRect) {
+ Rect insets = dp.getInsets();
+ int topMargin = dp.overviewTaskThumbnailTopMarginPx;
+ int bottomMargin = dp.getOverviewActionsClaimedSpace();
+ if (dp.isTaskbarPresent && Flags.enableGridOnlyOverview()) {
+ topMargin += context.getResources().getDimensionPixelSize(
+ R.dimen.overview_top_margin_grid_only);
+ bottomMargin += context.getResources().getDimensionPixelSize(
+ R.dimen.overview_bottom_margin_grid_only);
+ }
+ int sideMargin = dp.overviewGridSideMargin;
+
+ outRect.set(0, 0, dp.widthPx, dp.heightPx);
+ outRect.inset(Math.max(insets.left, sideMargin), insets.top + topMargin,
+ Math.max(insets.right, sideMargin), Math.max(insets.bottom, bottomMargin));
+ }
+
+ /**
+ * Calculates the overview grid non-focused task size for the provided device configuration.
+ */
+ public final void calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect,
+ RecentsPagedOrientationHandler orientationHandler) {
+ Resources res = context.getResources();
+ Rect potentialTaskRect = new Rect();
+ if (Flags.enableGridOnlyOverview()) {
+ calculateGridSize(dp, context, potentialTaskRect);
+ } else {
+ calculateFocusTaskSize(context, dp, potentialTaskRect);
+ }
+
+ float rowHeight = (potentialTaskRect.height() + dp.overviewTaskThumbnailTopMarginPx
+ - dp.overviewRowSpacing) / 2f;
+
+ PointF taskDimension = getTaskDimension(context, dp);
+ float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y;
+ int outWidth = Math.round(scale * taskDimension.x);
+ int outHeight = Math.round(scale * taskDimension.y);
+
+ int gravity = Gravity.TOP;
+ gravity |= orientationHandler.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
+ Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
+ }
+
+ /**
+ * Calculates the modal taskView size for the provided device configuration
+ */
+ public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect,
+ RecentsPagedOrientationHandler orientationHandler) {
+ calculateTaskSize(context, dp, outRect, orientationHandler);
+ boolean isGridOnlyOverview = dp.isTablet && Flags.enableGridOnlyOverview();
+ int claimedSpaceBelow = isGridOnlyOverview
+ ? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight
+ : (dp.heightPx - outRect.bottom - dp.getInsets().bottom);
+ int minimumHorizontalPadding = 0;
+ if (!isGridOnlyOverview) {
+ float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale);
+ minimumHorizontalPadding =
+ Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2);
+ }
+ calculateTaskSizeInternal(
+ context,
+ dp,
+ dp.overviewTaskMarginPx,
+ claimedSpaceBelow,
+ minimumHorizontalPadding,
+ 1f /*maxScale*/,
+ Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
+ outRect,
+ orientationHandler);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt
deleted file mode 100644
index 2fc4d04..0000000
--- a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep
-
-import android.content.Context
-import android.util.Log
-import com.android.launcher3.LauncherAppState
-import com.android.launcher3.LauncherPrefs
-import com.android.launcher3.moveStartupDataToDeviceProtectedStorageIsEnabled
-import com.android.launcher3.util.LockedUserState
-
-/**
- * Loads expensive objects in memory before the user is unlocked. This decreases experienced latency
- * when starting the launcher for the first time after a reboot.
- */
-object BootAwarePreloader {
- private const val TAG = "BootAwarePreloader"
-
- @JvmStatic
- fun start(context: Context) {
- val lp = LauncherPrefs.get(context)
- when {
- LockedUserState.get(context).isUserUnlocked ||
- !moveStartupDataToDeviceProtectedStorageIsEnabled -> {
- /* No-Op */
- }
- lp.isStartupDataMigrated -> {
- Log.d(TAG, "preloading start up data")
- LauncherAppState.INSTANCE.get(context)
- }
- else -> {
- Log.d(TAG, "queuing start up data migration to boot aware prefs")
- LockedUserState.get(context).runOnUserUnlocked {
- lp.migrateStartupDataToDeviceProtectedStorage()
- }
- }
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
new file mode 100644
index 0000000..9c188f3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.view.View
+import com.android.launcher3.AbstractFloatingViewHelper
+import com.android.launcher3.R
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.popup.SystemShortcut
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView.TaskContainer
+import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.DesktopModeStatus
+
+/** A menu item, "Desktop", that allows the user to bring the current app into Desktop Windowing. */
+class DesktopSystemShortcut(
+ container: RecentsViewContainer,
+ private val taskContainer: TaskContainer,
+ abstractFloatingViewHelper: AbstractFloatingViewHelper
+) :
+ SystemShortcut<RecentsViewContainer>(
+ R.drawable.ic_caption_desktop_button_foreground,
+ R.string.recent_task_option_desktop,
+ container,
+ taskContainer.itemInfo,
+ taskContainer.taskView,
+ abstractFloatingViewHelper
+ ) {
+ override fun onClick(view: View) {
+ dismissTaskMenuView()
+ val recentsView = mTarget.getOverviewPanel<RecentsView<*, *>>()
+ recentsView.moveTaskToDesktop(
+ taskContainer,
+ DesktopModeTransitionSource.APP_FROM_OVERVIEW
+ ) {
+ mTarget.statsLogManager
+ .logger()
+ .withItemInfo(taskContainer.itemInfo)
+ .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP)
+ }
+ }
+
+ companion object {
+ /** Creates a factory for creating Desktop system shortcuts. */
+ @JvmOverloads
+ fun createFactory(
+ abstractFloatingViewHelper: AbstractFloatingViewHelper = AbstractFloatingViewHelper()
+ ): TaskShortcutFactory {
+ return object : TaskShortcutFactory {
+ override fun getShortcuts(
+ 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
+ )
+ )
+ }
+
+ override fun showForGroupedTask() = true
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
new file mode 100644
index 0000000..3549a12
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import com.android.quickstep.util.DeviceConfigHelper
+import com.android.quickstep.util.DeviceConfigHelper.PropReader
+import java.io.PrintWriter
+
+/** Various configurations specific to nav-bar functionalities */
+class DeviceConfigWrapper private constructor(propReader: PropReader) {
+
+ val customLpnhThresholds =
+ propReader.get(
+ "CUSTOM_LPNH_THRESHOLDS",
+ true,
+ "Add dev options and server side control to customize the LPNH trigger slop and milliseconds"
+ )
+
+ val customLphThresholds =
+ propReader.get(
+ "CUSTOM_LPH_THRESHOLDS",
+ true,
+ "Server side control to customize LPH timeout and touch slop"
+ )
+
+ val customLpaaThresholds =
+ propReader.get(
+ "CUSTOM_LPAA_THRESHOLDS",
+ false,
+ "Server side control to customize LPAA timeout and touch slop"
+ )
+
+ val overrideLpnhLphThresholds =
+ propReader.get(
+ "OVERRIDE_LPNH_LPH_THRESHOLDS",
+ false,
+ "Enable AGSA override for LPNH and LPH timeout and touch slop"
+ )
+
+ val lpnhTimeoutMs =
+ propReader.get("LPNH_TIMEOUT_MS", 450, "Controls lpnh timeout in milliseconds")
+
+ val lpnhSlopPercentage =
+ propReader.get("LPNH_SLOP_PERCENTAGE", 100, "Controls touch slop percentage for lpnh")
+
+ val enableLpnhTwoStages =
+ propReader.get(
+ "ENABLE_LPNH_TWO_STAGES",
+ false,
+ "Enable two stage for LPNH duration and touch slop"
+ )
+
+ val twoStageMultiplier =
+ propReader.get(
+ "TWO_STAGE_MULTIPLIER",
+ 2,
+ "Extends the duration and touch slop if the initial slop is passed"
+ )
+
+ val animateLpnh = propReader.get("ANIMATE_LPNH", false, "Animates navbar when long pressing")
+
+ val shrinkNavHandleOnPress =
+ propReader.get(
+ "SHRINK_NAV_HANDLE_ON_PRESS",
+ false,
+ "Shrinks navbar when long pressing if ANIMATE_LPNH is enabled"
+ )
+
+ val enableLongPressNavHandle =
+ propReader.get(
+ "ENABLE_LONG_PRESS_NAV_HANDLE",
+ true,
+ "Enables long pressing on the bottom bar nav handle to trigger events."
+ )
+
+ val enableSearchHapticHint =
+ propReader.get(
+ "ENABLE_SEARCH_HAPTIC_HINT",
+ true,
+ "Enables haptic hint while long pressing on the bottom bar nav handle."
+ )
+
+ val enableSearchHapticCommit =
+ propReader.get(
+ "ENABLE_SEARCH_HAPTIC_COMMIT",
+ true,
+ "Enables haptic hint at end of long pressing on the bottom bar nav handle."
+ )
+
+ val lpnhHapticHintStartScalePercent =
+ propReader.get("LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0, "Haptic hint start scale.")
+
+ val lpnhHapticHintEndScalePercent =
+ propReader.get("LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100, "Haptic hint end scale.")
+
+ val lpnhHapticHintScaleExponent =
+ propReader.get("LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1, "Haptic hint scale exponent.")
+
+ val lpnhHapticHintIterations =
+ propReader.get("LPNH_HAPTIC_HINT_ITERATIONS", 50, "Haptic hint number of iterations.")
+
+ val enableLpnhDeepPress =
+ propReader.get(
+ "ENABLE_LPNH_DEEP_PRESS",
+ true,
+ "Long press of nav handle is instantly triggered if deep press is detected."
+ )
+
+ val lpnhHapticHintDelay =
+ propReader.get("LPNH_HAPTIC_HINT_DELAY", 0, "Delay before haptic hint starts.")
+
+ val lpnhExtraTouchWidthDp =
+ propReader.get(
+ "LPNH_EXTRA_TOUCH_WIDTH_DP",
+ 0,
+ "Controls extra dp on the nav bar sides to trigger LPNH. Can be negative for a smaller touch region."
+ )
+
+ val allAppsOverviewThreshold =
+ propReader.get(
+ "ALL_APPS_OVERVIEW_THRESHOLD",
+ 180,
+ "Threshold to open All Apps from Overview"
+ )
+
+ /** Dump config values. */
+ fun dump(prefix: String, writer: PrintWriter) {
+ writer.println("$prefix DeviceConfigWrapper:")
+ writer.println("$prefix\tcustomLpnhThresholds=$customLpnhThresholds")
+ writer.println("$prefix\tcustomLphThresholds=$customLphThresholds")
+ writer.println("$prefix\toverrideLpnhLphThresholds=$overrideLpnhLphThresholds")
+ writer.println("$prefix\tlpnhSlopPercentage=$lpnhSlopPercentage")
+ writer.println("$prefix\tanimateLpnh=$animateLpnh")
+ writer.println("$prefix\tshrinkNavHandleOnPress=$shrinkNavHandleOnPress")
+ writer.println("$prefix\tlpnhTimeoutMs=$lpnhTimeoutMs")
+ writer.println("$prefix\tenableLongPressNavHandle=$enableLongPressNavHandle")
+ writer.println("$prefix\tenableSearchHapticHint=$enableSearchHapticHint")
+ writer.println("$prefix\tenableSearchHapticCommit=$enableSearchHapticCommit")
+ writer.println("$prefix\tlpnhHapticHintStartScalePercent=$lpnhHapticHintStartScalePercent")
+ writer.println("$prefix\tlpnhHapticHintEndScalePercent=$lpnhHapticHintEndScalePercent")
+ writer.println("$prefix\tlpnhHapticHintScaleExponent=$lpnhHapticHintScaleExponent")
+ writer.println("$prefix\tlpnhHapticHintIterations=$lpnhHapticHintIterations")
+ writer.println("$prefix\tenableLpnhDeepPress=$enableLpnhDeepPress")
+ writer.println("$prefix\tlpnhHapticHintDelay=$lpnhHapticHintDelay")
+ writer.println("$prefix\tlpnhExtraTouchWidthDp=$lpnhExtraTouchWidthDp")
+ writer.println("$prefix\tallAppsOverviewThreshold=$allAppsOverviewThreshold")
+ }
+
+ companion object {
+ @JvmStatic val configHelper by lazy { DeviceConfigHelper(::DeviceConfigWrapper) }
+
+ @JvmStatic fun get() = configHelper.config
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 27e8726..89fbf4a 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -96,13 +96,13 @@
@Nullable
@Override
- public RecentsActivity getCreatedActivity() {
+ public RecentsActivity getCreatedContainer() {
return RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
}
@Override
public FallbackTaskbarUIController getTaskbarController() {
- RecentsActivity activity = getCreatedActivity();
+ RecentsActivity activity = getCreatedContainer();
if (activity == null) {
return null;
}
@@ -112,7 +112,7 @@
@Nullable
@Override
public RecentsView getVisibleRecentsView() {
- RecentsActivity activity = getCreatedActivity();
+ RecentsActivity activity = getCreatedContainer();
if (activity != null) {
if (activity.hasBeenResumed() || isInLiveTileMode()) {
return activity.getOverviewPanel();
@@ -154,7 +154,8 @@
@Override
public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
- final StateManager<RecentsState> stateManager = getCreatedActivity().getStateManager();
+ final StateManager<RecentsState, RecentsActivity> stateManager =
+ getCreatedContainer().getStateManager();
if (stateManager.getState() == HOME) {
exitRunnable.run();
notifyRecentsOfOrientation(deviceState);
@@ -177,7 +178,7 @@
@Override
public boolean isInLiveTileMode() {
- RecentsActivity activity = getCreatedActivity();
+ RecentsActivity activity = getCreatedContainer();
return activity != null && activity.getStateManager().getState() == DEFAULT &&
activity.isStarted();
}
@@ -185,7 +186,7 @@
@Override
public void onLaunchTaskFailed() {
// TODO: probably go back to overview instead.
- RecentsActivity activity = getCreatedActivity();
+ RecentsActivity activity = getCreatedContainer();
if (activity == null) {
return;
}
@@ -209,7 +210,7 @@
private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
// reset layout on swipe to home
- RecentsView recentsView = getCreatedActivity().getOverviewPanel();
+ RecentsView recentsView = getCreatedContainer().getOverviewPanel();
recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
rotationTouchHelper.getDisplayRotation());
}
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index b42eb06..625b6c6 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -68,11 +68,12 @@
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.system.InputConsumerController;
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
+import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
@@ -140,9 +141,13 @@
}
@Override
- protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies,
- long duration, boolean isTargetTranslucent, boolean appCanEnterPip,
- RemoteAnimationTarget runningTaskTarget) {
+ protected HomeAnimationFactory createHomeAnimationFactory(
+ List<IBinder> launchCookies,
+ long duration,
+ boolean isTargetTranslucent,
+ boolean appCanEnterPip,
+ RemoteAnimationTarget runningTaskTarget,
+ @Nullable TaskView targetTaskView) {
mAppCanEnterPip = appCanEnterPip;
if (appCanEnterPip) {
return new FallbackPipToHomeAnimationFactory();
@@ -223,7 +228,7 @@
public AnimatorPlaybackController createActivityAnimationToHome() {
// copied from {@link LauncherSwipeHandlerV2.LauncherHomeAnimationFactory}
long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
- return mActivity.getStateManager().createAnimationToNewWorkspace(
+ return mContainer.getStateManager().createAnimationToNewWorkspace(
RecentsState.HOME, accuracy, StateAnimationConfig.SKIP_ALL_ANIMATIONS);
}
}
@@ -380,7 +385,7 @@
}
@Override
- public void update(RectF currentRect, float progress, float radius) {
+ public void update(RectF currentRect, float progress, float radius, int overlayAlpha) {
if (mSurfaceControl != null) {
currentRect.roundOut(mTempRect);
Transaction t = new Transaction();
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index d02909c..81c9d4a 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -30,6 +30,7 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
import android.content.Intent;
+import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
@@ -37,10 +38,10 @@
import androidx.annotation.Nullable;
import com.android.launcher3.statemanager.BaseState;
-import com.android.launcher3.statemanager.StatefulActivity;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.io.PrintWriter;
@@ -151,7 +152,7 @@
// Needed to interact with the current activity
private final Intent mHomeIntent;
private final Intent mOverviewIntent;
- private final BaseActivityInterface mActivityInterface;
+ private final BaseContainerInterface mContainerInterface;
private final MultiStateCallback mStateCallback;
private final int mGestureId;
@@ -182,14 +183,15 @@
private HashMap<Integer, ThumbnailData> mRecentsAnimationCanceledSnapshots;
/** The time when the swipe up gesture is triggered. */
- private long mSwipeUpStartTimeMs;
+ private final long mSwipeUpStartTimeMs = SystemClock.uptimeMillis();
private boolean mHandlingAtomicEvent;
+ private boolean mIsInExtendedSlopRegion;
public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
mHomeIntent = componentObserver.getHomeIntent();
mOverviewIntent = componentObserver.getOverviewIntent();
- mActivityInterface = componentObserver.getActivityInterface();
+ mContainerInterface = componentObserver.getActivityInterface();
mStateCallback = new MultiStateCallback(
STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
mGestureId = gestureId;
@@ -198,7 +200,7 @@
public GestureState(GestureState other) {
mHomeIntent = other.mHomeIntent;
mOverviewIntent = other.mOverviewIntent;
- mActivityInterface = other.mActivityInterface;
+ mContainerInterface = other.mContainerInterface;
mStateCallback = other.mStateCallback;
mGestureId = other.mGestureId;
mRunningTask = other.mRunningTask;
@@ -212,7 +214,7 @@
// Do nothing, only used for initializing the gesture state prior to user unlock
mHomeIntent = new Intent();
mOverviewIntent = new Intent();
- mActivityInterface = null;
+ mContainerInterface = null;
mStateCallback = new MultiStateCallback(
STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
mGestureId = -1;
@@ -268,9 +270,9 @@
/**
* @return the interface to the activity handing the UI updates for this gesture.
*/
- public <S extends BaseState<S>,
- T extends StatefulActivity<S>> BaseActivityInterface<S, T> getActivityInterface() {
- return mActivityInterface;
+ public <S extends BaseState<S>, T extends RecentsViewContainer>
+ BaseContainerInterface<S, T> getContainerInterface() {
+ return mContainerInterface;
}
/**
@@ -493,6 +495,25 @@
}
/**
+ * Set whether it's in long press nav handle (LPNH)'s extended touch slop region, e.g., second
+ * stage region in order to continue respect LPNH and ignore other touch slop logic.
+ * This will only be set to true when flag ENABLE_LPNH_TWO_STAGES is turned on.
+ */
+ public void setIsInExtendedSlopRegion(boolean isInExtendedSlopRegion) {
+ if (DeviceConfigWrapper.get().getEnableLpnhTwoStages()) {
+ mIsInExtendedSlopRegion = isInExtendedSlopRegion;
+ }
+ }
+
+ /**
+ * Returns whether it's in LPNH's extended touch slop region. This is only valid when flag
+ * ENABLE_LPNH_TWO_STAGES is turned on.
+ */
+ public boolean isInExtendedSlopRegion() {
+ return mIsInExtendedSlopRegion;
+ }
+
+ /**
* Returns and clears the canceled animation thumbnail data. This call only returns a value
* while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is responsible for
* calling {@link RecentsAnimationController#cleanupScreenshot()}.
@@ -508,21 +529,18 @@
return null;
}
- void setSwipeUpStartTimeMs(long uptimeMs) {
- mSwipeUpStartTimeMs = uptimeMs;
- }
-
long getSwipeUpStartTimeMs() {
return mSwipeUpStartTimeMs;
}
- public void dump(PrintWriter pw) {
- pw.println("GestureState:");
- pw.println(" gestureID=" + mGestureId);
- pw.println(" runningTask=" + mRunningTask);
- pw.println(" endTarget=" + mEndTarget);
- pw.println(" lastAppearedTaskTargetId=" + Arrays.toString(mLastAppearedTaskTargets));
- pw.println(" lastStartedTaskId=" + Arrays.toString(mLastStartedTaskId));
- pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning());
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "GestureState:");
+ pw.println(prefix + "\tgestureID=" + mGestureId);
+ pw.println(prefix + "\trunningTask=" + mRunningTask);
+ pw.println(prefix + "\tendTarget=" + mEndTarget);
+ pw.println(prefix + "\tlastAppearedTaskTargetId="
+ + Arrays.toString(mLastAppearedTaskTargets));
+ pw.println(prefix + "\tlastStartedTaskId=" + Arrays.toString(mLastStartedTaskId));
+ pw.println(prefix + "\tisRecentsAnimationRunning=" + isRecentsAnimationRunning());
}
}
diff --git a/quickstep/src/com/android/quickstep/HomeVisibilityState.kt b/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
new file mode 100644
index 0000000..241e16d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.os.RemoteException
+import android.util.Log
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.util.Executors
+import com.android.wm.shell.shared.IHomeTransitionListener.Stub
+import com.android.wm.shell.shared.IShellTransitions
+
+/** Class to track visibility state of Launcher */
+class HomeVisibilityState {
+
+ var isHomeVisible = true
+ private set
+
+ private var listeners = mutableSetOf<VisibilityChangeListener>()
+
+ fun addListener(l: VisibilityChangeListener) = listeners.add(l)
+
+ fun removeListener(l: VisibilityChangeListener) = listeners.remove(l)
+
+ fun init(transitions: IShellTransitions?) {
+ if (!FeatureFlags.enableHomeTransitionListener()) return
+ try {
+ transitions?.setHomeTransitionListener(
+ object : Stub() {
+ override fun onHomeVisibilityChanged(isVisible: Boolean) {
+ Executors.MAIN_EXECUTOR.execute {
+ isHomeVisible = isVisible
+ listeners.forEach { it.onHomeVisibilityChanged(isVisible) }
+ }
+ }
+ }
+ )
+ } catch (e: RemoteException) {
+ Log.w(TAG, "Failed call setHomeTransitionListener", e)
+ }
+ }
+
+ interface VisibilityChangeListener {
+ fun onHomeVisibilityChanged(isVisible: Boolean)
+ }
+
+ override fun toString() = "{HomeVisibilityState isHomeVisible=$isHomeVisible}"
+
+ companion object {
+
+ private const val TAG = "HomeVisibilityState"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 97c48e6..b564fa7 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -85,7 +85,7 @@
@Override
public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {
- Launcher launcher = getCreatedActivity();
+ QuickstepLauncher launcher = getCreatedContainer();
if (launcher == null) {
return;
}
@@ -102,7 +102,7 @@
@Override
public void onAssistantVisibilityChanged(float visibility) {
- Launcher launcher = getCreatedActivity();
+ QuickstepLauncher launcher = getCreatedContainer();
if (launcher == null) {
return;
}
@@ -144,7 +144,7 @@
@Override
public void setOnDeferredActivityLaunchCallback(Runnable r) {
- Launcher launcher = getCreatedActivity();
+ QuickstepLauncher launcher = getCreatedContainer();
if (launcher == null) {
return;
}
@@ -153,14 +153,14 @@
@Nullable
@Override
- public QuickstepLauncher getCreatedActivity() {
+ public QuickstepLauncher getCreatedContainer() {
return QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
}
@Nullable
@Override
public DepthController getDepthController() {
- QuickstepLauncher launcher = getCreatedActivity();
+ QuickstepLauncher launcher = getCreatedContainer();
if (launcher == null) {
return null;
}
@@ -170,7 +170,7 @@
@Nullable
@Override
public DesktopVisibilityController getDesktopVisibilityController() {
- QuickstepLauncher launcher = getCreatedActivity();
+ QuickstepLauncher launcher = getCreatedContainer();
if (launcher == null) {
return null;
}
@@ -180,7 +180,7 @@
@Nullable
@Override
public LauncherTaskbarUIController getTaskbarController() {
- QuickstepLauncher launcher = getCreatedActivity();
+ QuickstepLauncher launcher = getCreatedContainer();
if (launcher == null) {
return null;
}
@@ -190,9 +190,9 @@
@Nullable
@Override
public RecentsView getVisibleRecentsView() {
- Launcher launcher = getVisibleLauncher();
+ QuickstepLauncher launcher = getVisibleLauncher();
RecentsView recentsView =
- launcher != null && launcher.getStateManager().getState().overviewUi
+ launcher != null && launcher.getStateManager().getState().isRecentsViewVisible
? launcher.getOverviewPanel() : null;
if (recentsView == null || (!launcher.hasBeenResumed()
&& recentsView.getRunningTaskViewId() == -1)) {
@@ -204,15 +204,24 @@
@Nullable
@UiThread
- private Launcher getVisibleLauncher() {
- Launcher launcher = getCreatedActivity();
- return (launcher != null) && launcher.isStarted()
- && (isInLiveTileMode() || launcher.hasBeenResumed()) ? launcher : null;
+ private QuickstepLauncher getVisibleLauncher() {
+ QuickstepLauncher launcher = getCreatedContainer();
+ if (launcher == null) {
+ return null;
+ }
+ if (launcher.isStarted() && (isInLiveTileMode() || launcher.hasBeenResumed())) {
+ return launcher;
+ }
+ if (isInMinusOne()) {
+ return launcher;
+ }
+
+ return null;
}
@Override
public boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener) {
- Launcher launcher = getVisibleLauncher();
+ QuickstepLauncher launcher = getVisibleLauncher();
if (launcher == null) {
return false;
}
@@ -233,7 +242,8 @@
@Override
public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
- final StateManager<LauncherState> stateManager = getCreatedActivity().getStateManager();
+ final StateManager<LauncherState, Launcher> stateManager =
+ getCreatedContainer().getStateManager();
stateManager.addStateListener(
new StateManager.StateListener<LauncherState>() {
@Override
@@ -250,7 +260,7 @@
private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
// reset layout on swipe to home
- RecentsView recentsView = getCreatedActivity().getOverviewPanel();
+ RecentsView recentsView = getCreatedContainer().getOverviewPanel();
recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
rotationTouchHelper.getDisplayRotation());
}
@@ -269,12 +279,12 @@
public boolean allowAllAppsFromOverview() {
return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
// If floating search bar would not show in overview, don't allow all apps gesture.
- && OVERVIEW.areElementsVisible(getCreatedActivity(), FLOATING_SEARCH_BAR);
+ && OVERVIEW.areElementsVisible(getCreatedContainer(), FLOATING_SEARCH_BAR);
}
@Override
public boolean isInLiveTileMode() {
- Launcher launcher = getCreatedActivity();
+ QuickstepLauncher launcher = getCreatedContainer();
return launcher != null
&& launcher.getStateManager().getState() == OVERVIEW
@@ -282,9 +292,18 @@
&& TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
}
+ private boolean isInMinusOne() {
+ QuickstepLauncher launcher = getCreatedContainer();
+
+ return launcher != null
+ && launcher.getStateManager().getState() == NORMAL
+ && !launcher.isStarted()
+ && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
+ }
+
@Override
public void onLaunchTaskFailed() {
- Launcher launcher = getCreatedActivity();
+ QuickstepLauncher launcher = getCreatedContainer();
if (launcher == null) {
return;
}
@@ -294,12 +313,13 @@
@Override
public void closeOverlay() {
super.closeOverlay();
- Launcher launcher = getCreatedActivity();
+ QuickstepLauncher launcher = getCreatedContainer();
if (launcher == null) {
return;
}
LauncherOverlayManager om = launcher.getOverlayManager();
- if (!launcher.isStarted() || launcher.isForceInvisible()) {
+ if (!SystemUiProxy.INSTANCE.get(launcher).getHomeVisibilityState().isHomeVisible()
+ || launcher.isForceInvisible()) {
om.hideOverlay(false /* animate */);
} else {
om.hideOverlay(150);
@@ -327,9 +347,8 @@
}
@Override
- protected int getOverviewScrimColorForState(QuickstepLauncher launcher,
- LauncherState state) {
- return state.getWorkspaceScrimColor(launcher);
+ protected int getOverviewScrimColorForState(QuickstepLauncher activity, LauncherState state) {
+ return state.getWorkspaceScrimColor(activity);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index cc5a923..b720382 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -39,12 +39,12 @@
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
+import android.view.Choreographer;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.View;
-import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
@@ -53,6 +53,7 @@
import android.window.IOnBackInvokedCallback;
import com.android.app.animation.Interpolators;
+import com.android.internal.policy.SystemBarUtils;
import com.android.internal.view.AppearanceRegion;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
@@ -84,27 +85,23 @@
*
*/
public class LauncherBackAnimationController {
- private static final int CANCEL_TRANSITION_DURATION = 233;
private static final int SCRIM_FADE_DURATION = 233;
private static final float MIN_WINDOW_SCALE = 0.85f;
private static final float MAX_SCRIM_ALPHA_DARK = 0.8f;
private static final float MAX_SCRIM_ALPHA_LIGHT = 0.2f;
- private static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.20f;
private final QuickstepTransitionManager mQuickstepTransitionManager;
private final Matrix mTransformMatrix = new Matrix();
/** The window position at the beginning of the back animation. */
private final Rect mStartRect = new Rect();
- /** The window position when the back gesture is cancelled. */
- private final RectF mCancelRect = new RectF();
/** The current window position. */
private final RectF mCurrentRect = new RectF();
private final QuickstepLauncher mLauncher;
private final int mWindowScaleMarginX;
private float mWindowScaleEndCornerRadius;
private float mWindowScaleStartCornerRadius;
- private final Interpolator mCancelInterpolator;
- private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE;
+ private int mStatusBarHeight;
+ private final Interpolator mProgressInterpolator = Interpolators.BACK_GESTURE;
private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator();
private final PointF mInitialTouchPos = new PointF();
@@ -127,7 +124,7 @@
private final ComponentCallbacks mComponentCallbacks = new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
- loadCornerRadius();
+ loadResources();
}
@Override
@@ -139,11 +136,9 @@
QuickstepTransitionManager quickstepTransitionManager) {
mLauncher = launcher;
mQuickstepTransitionManager = quickstepTransitionManager;
- loadCornerRadius();
+ loadResources();
mWindowScaleMarginX = mLauncher.getResources().getDimensionPixelSize(
R.dimen.swipe_back_window_scale_x_margin);
- mCancelInterpolator =
- AnimationUtils.loadInterpolator(mLauncher, R.interpolator.standard_interpolator);
}
/**
@@ -181,8 +176,7 @@
mHandler.post(() -> {
LauncherBackAnimationController controller = mControllerRef.get();
if (controller != null) {
- mProgressAnimator.onBackCancelled(
- controller::resetPositionAnimated);
+ mProgressAnimator.onBackCancelled(controller::onCancelFinished);
}
});
}
@@ -227,6 +221,11 @@
}
});
}
+
+ @Override
+ public void setTriggerBack(boolean triggerBack) {
+ // TODO(b/261654570): track touch from the Launcher process.
+ }
}
private static class RemoteAnimationRunnerStub extends IRemoteAnimationRunner.Stub {
@@ -262,24 +261,9 @@
public void onAnimationCancelled() {}
}
- private void resetPositionAnimated() {
- ValueAnimator cancelAnimator = ValueAnimator.ofFloat(0, 1);
- mCancelRect.set(mCurrentRect);
- cancelAnimator.setDuration(CANCEL_TRANSITION_DURATION);
- cancelAnimator.setInterpolator(mCancelInterpolator);
- cancelAnimator.addUpdateListener(
- animation -> {
- updateCancelProgress((float) animation.getAnimatedValue());
- });
- cancelAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Refresh the status bar appearance to the original one.
- customizeStatusBarAppearance(false);
- finishAnimation();
- }
- });
- cancelAnimator.start();
+ private void onCancelFinished() {
+ customizeStatusBarAppearance(false);
+ finishAnimation();
}
/** Unregisters the back to launcher callback in shell. */
@@ -292,6 +276,14 @@
}
private void startBack(BackMotionEvent backEvent) {
+ // in case we're still animating an onBackCancelled event, let's remove the finish-
+ // callback from the progress animator to prevent calling finishAnimation() before
+ // restarting a new animation
+ // Side note: startBack is never called during the post-commit phase if the back gesture
+ // was committed (not cancelled). BackAnimationController prevents that. Therefore we
+ // don't have to handle that case.
+ mProgressAnimator.removeOnBackCancelledFinishCallback();
+
mBackInProgress = true;
RemoteAnimationTarget appTarget = backEvent.getDepartingAnimationTarget();
@@ -314,8 +306,10 @@
new RemoteAnimationTarget[]{ mBackTarget });
setLauncherTargetViewVisible(false);
mCurrentRect.set(mStartRect);
- addScrimLayer();
- mTransaction.apply();
+ if (mScrimLayer == null) {
+ addScrimLayer();
+ }
+ applyTransaction();
}
private void setLauncherTargetViewVisible(boolean isVisible) {
@@ -355,7 +349,8 @@
return;
}
if (mScrimLayer.isValid()) {
- mTransaction.remove(mScrimLayer).apply();
+ mTransaction.remove(mScrimLayer);
+ applyTransaction();
}
mScrimLayer = null;
}
@@ -394,24 +389,7 @@
progress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
applyTransform(mCurrentRect, cornerRadius);
- customizeStatusBarAppearance(progress > UPDATE_SYSUI_FLAGS_THRESHOLD);
- }
-
- private void updateCancelProgress(float progress) {
- if (mBackTarget == null) {
- return;
- }
- mCurrentRect.set(
- Utilities.mapRange(progress, mCancelRect.left, mStartRect.left),
- Utilities.mapRange(progress, mCancelRect.top, mStartRect.top),
- Utilities.mapRange(progress, mCancelRect.right, mStartRect.right),
- Utilities.mapRange(progress, mCancelRect.bottom, mStartRect.bottom));
-
- float endCornerRadius = Utilities.mapRange(
- mBackProgress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
- float cornerRadius = Utilities.mapRange(
- progress, endCornerRadius, mWindowScaleStartCornerRadius);
- applyTransform(mCurrentRect, cornerRadius);
+ customizeStatusBarAppearance(top > mStatusBarHeight / 2);
}
/** Transform the target window to match the target rect. */
@@ -426,7 +404,11 @@
mTransaction.setWindowCrop(mBackTarget.leash, mStartRect);
mTransaction.setCornerRadius(mBackTarget.leash, cornerRadius);
}
+ applyTransaction();
+ }
+ private void applyTransaction() {
+ mTransaction.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
mTransaction.apply();
}
@@ -484,7 +466,6 @@
mBackInProgress = false;
mBackProgress = 0;
mTransformMatrix.reset();
- mCancelRect.setEmpty();
mCurrentRect.setEmpty();
mStartRect.setEmpty();
mInitialTouchPos.set(0, 0);
@@ -542,7 +523,7 @@
float value = (Float) animation.getAnimatedValue();
if (mScrimLayer != null && mScrimLayer.isValid()) {
mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha);
- mTransaction.apply();
+ applyTransaction();
}
});
mScrimAlphaAnimator.addListener(new AnimatorListenerAdapter() {
@@ -555,13 +536,14 @@
anim.start();
}
- private void loadCornerRadius() {
+ private void loadResources() {
mWindowScaleEndCornerRadius = QuickStepContract.supportsRoundedCornersOnWindows(
mLauncher.getResources())
? mLauncher.getResources().getDimensionPixelSize(
R.dimen.swipe_back_window_corner_radius)
: 0;
mWindowScaleStartCornerRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
+ mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mLauncher);
}
/**
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index a9d8afc..3c66590 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -17,6 +17,7 @@
import static com.android.app.animation.Interpolators.EXAGGERATED_EASE;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.mapBoundToRange;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
@@ -42,10 +43,12 @@
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ObjectWrapper;
+import com.android.launcher3.views.ClipIconView;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.FloatingView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.views.FloatingWidgetView;
@@ -53,7 +56,8 @@
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.InputConsumerController;
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Temporary class to allow easier refactoring
@@ -70,10 +74,14 @@
@Override
- protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies,
- long duration, boolean isTargetTranslucent, boolean appCanEnterPip,
- RemoteAnimationTarget runningTaskTarget) {
- if (mActivity == null) {
+ protected HomeAnimationFactory createHomeAnimationFactory(
+ List<IBinder> launchCookies,
+ long duration,
+ boolean isTargetTranslucent,
+ boolean appCanEnterPip,
+ RemoteAnimationTarget runningTaskTarget,
+ @Nullable TaskView targetTaskView) {
+ if (mContainer == null) {
mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
return new HomeAnimationFactory() {
@@ -84,32 +92,49 @@
};
}
- final View workspaceView = findWorkspaceView(launchCookies,
- mRecentsView.getRunningTaskView());
- boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow()
- && workspaceView.getHeight() > 0;
+ TaskView sourceTaskView = mRecentsView == null && targetTaskView == null
+ ? null
+ : targetTaskView == null
+ ? mRecentsView.getRunningTaskView()
+ : targetTaskView;
+ final View workspaceView = findWorkspaceView(
+ targetTaskView == null ? launchCookies : Collections.emptyList(),
+ sourceTaskView);
+ boolean canUseWorkspaceView = workspaceView != null
+ && workspaceView.isAttachedToWindow()
+ && workspaceView.getHeight() > 0
+ && (mContainer.getDesktopVisibilityController() == null
+ || !mContainer.getDesktopVisibilityController().areDesktopTasksVisible());
- mActivity.getRootView().setForceHideBackArrow(true);
+ mContainer.getRootView().setForceHideBackArrow(true);
if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
- mActivity.setHintUserWillBeActive();
+ mContainer.setHintUserWillBeActive();
}
if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
- return new LauncherHomeAnimationFactory();
+ return new LauncherHomeAnimationFactory() {
+
+ @Nullable
+ @Override
+ public TaskView getTargetTaskView() {
+ return targetTaskView;
+ }
+ };
}
if (workspaceView instanceof LauncherAppWidgetHostView) {
return createWidgetHomeAnimationFactory((LauncherAppWidgetHostView) workspaceView,
isTargetTranslucent, runningTaskTarget);
}
- return createIconHomeAnimationFactory(workspaceView);
+ return createIconHomeAnimationFactory(workspaceView, targetTaskView);
}
- private HomeAnimationFactory createIconHomeAnimationFactory(View workspaceView) {
+ private HomeAnimationFactory createIconHomeAnimationFactory(
+ View workspaceView, @Nullable TaskView targetTaskView) {
RectF iconLocation = new RectF();
- FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView, null,
- mActivity.getTaskbarUIController() == null
+ FloatingIconView floatingIconView = getFloatingIconView(mContainer, workspaceView, null,
+ mContainer.getTaskbarUIController() == null
? null
- : mActivity.getTaskbarUIController().findMatchingView(workspaceView),
+ : mContainer.getTaskbarUIController().findMatchingView(workspaceView),
true /* hideOriginal */, iconLocation, false /* isOpening */);
// We want the window alpha to be 0 once this threshold is met, so that the
@@ -117,6 +142,10 @@
float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
return new FloatingViewHomeAnimationFactory(floatingIconView) {
+ @Nullable
+ private RectF mTargetRect;
+ @Nullable
+ private RectFSpringAnim mSiblingAnimation;
@Nullable
@Override
@@ -133,22 +162,64 @@
@NonNull
@Override
public RectF getWindowTargetRect() {
- return iconLocation;
+ if (enableScalingRevealHomeAnimation()) {
+ if (mTargetRect == null) {
+ mTargetRect = new RectF(iconLocation);
+ }
+ return mTargetRect;
+ } else {
+ return iconLocation;
+ }
+ }
+
+ @Override
+ protected void playScalingRevealAnimation() {
+ if (mContainer != null) {
+ new ScalingWorkspaceRevealAnim(mContainer, mSiblingAnimation,
+ getWindowTargetRect()).start();
+ }
}
@Override
public void setAnimation(RectFSpringAnim anim) {
super.setAnimation(anim);
- anim.addAnimatorListener(floatingIconView);
- floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
- floatingIconView.setFastFinishRunnable(anim::end);
+ mSiblingAnimation = anim;
+ mSiblingAnimation.addAnimatorListener(floatingIconView);
+ floatingIconView.setOnTargetChangeListener(
+ mSiblingAnimation::onTargetPositionChanged);
+ floatingIconView.setFastFinishRunnable(mSiblingAnimation::end);
}
@Override
- public void update(RectF currentRect, float progress, float radius) {
- super.update(currentRect, progress, radius);
+ public void update(
+ RectF currentRect,
+ float progress,
+ float radius,
+ int overlayAlpha) {
floatingIconView.update(1f /* alpha */, currentRect, progress, windowAlphaThreshold,
- radius, false);
+ radius, false, overlayAlpha);
+ }
+
+ @Override
+ public boolean isAnimationReady() {
+ return floatingIconView.isLaidOut();
+ }
+
+ @Override
+ public void setTaskViewArtist(ClipIconView.TaskViewArtist taskViewArtist) {
+ super.setTaskViewArtist(taskViewArtist);
+ floatingIconView.setOverlayArtist(taskViewArtist);
+ }
+
+ @Override
+ public boolean isAnimatingIntoIcon() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public TaskView getTargetTaskView() {
+ return targetTaskView;
}
};
}
@@ -169,7 +240,7 @@
Size windowSize = new Size(crop.width(), crop.height());
int fallbackBackgroundColor =
FloatingWidgetView.getDefaultBackgroundColor(mContext, runningTaskTarget);
- FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mActivity,
+ FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mContainer,
hostView, backgroundLocation, windowSize, tvs.getCurrentCornerRadius(),
isTargetTranslucent, fallbackBackgroundColor);
@@ -202,8 +273,8 @@
}
@Override
- public void update(RectF currentRect, float progress, float radius) {
- super.update(currentRect, progress, radius);
+ public void update(RectF currentRect, float progress, float radius, int overlayAlpha) {
+ super.update(currentRect, progress, radius, overlayAlpha);
final float fallbackBackgroundAlpha =
1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE);
final float foregroundAlpha =
@@ -224,13 +295,12 @@
* associated with the running task.
*/
@Nullable
- private View findWorkspaceView(ArrayList<IBinder> launchCookies, TaskView runningTaskView) {
+ private View findWorkspaceView(List<IBinder> launchCookies, TaskView sourceTaskView) {
if (mIsSwipingPipToHome) {
// Disable if swiping to PIP
return null;
}
- if (runningTaskView == null || runningTaskView.getTask() == null
- || runningTaskView.getTask().key.getComponent() == null) {
+ if (sourceTaskView == null || sourceTaskView.getFirstTask().key.getComponent() == null) {
// Disable if it's an invalid task
return null;
}
@@ -246,9 +316,9 @@
}
}
- return mActivity.getFirstMatchForAppClose(launchCookieItemId,
- runningTaskView.getTask().key.getComponent().getPackageName(),
- UserHandle.of(runningTaskView.getTask().key.userId),
+ return mContainer.getFirstMatchForAppClose(launchCookieItemId,
+ sourceTaskView.getFirstTask().key.getComponent().getPackageName(),
+ UserHandle.of(sourceTaskView.getFirstTask().key.userId),
false /* supportsAllAppsState */);
}
@@ -290,15 +360,31 @@
// Return an empty APC here since we have an non-user controlled animation
// to home.
long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
- return mActivity.getStateManager().createAnimationToNewWorkspace(
+ return mContainer.getStateManager().createAnimationToNewWorkspace(
NORMAL, accuracy, StateAnimationConfig.SKIP_ALL_ANIMATIONS);
}
@Override
public void playAtomicAnimation(float velocity) {
- new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */,
- getViewIgnoredInWorkspaceRevealAnimation())
- .start();
+ if (enableScalingRevealHomeAnimation()) {
+ playScalingRevealAnimation();
+ } else {
+ new StaggeredWorkspaceAnim(mContainer, velocity, true /* animateOverviewScrim */,
+ getViewIgnoredInWorkspaceRevealAnimation())
+ .start();
+ }
+ }
+
+ /**
+ * Extracted in a different method so subclasses that have a custom window animation with a
+ * target (icons, widgets) can pass the optional parameters.
+ */
+ protected void playScalingRevealAnimation() {
+ if (mContainer != null) {
+ new ScalingWorkspaceRevealAnim(
+ mContainer, null /* siblingAnimation */,
+ null /* windowTargetRect */).start();
+ }
}
}
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 56c9a00..7da92bc 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -16,6 +16,9 @@
package com.android.quickstep;
import static com.android.launcher3.PagedView.INVALID_PAGE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
@@ -25,21 +28,26 @@
import android.graphics.PointF;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.Log;
import android.view.View;
import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -52,6 +60,7 @@
* Helper class to handle various atomic commands for switching between Overview.
*/
public class OverviewCommandHelper {
+ private static final String TAG = "OverviewCommandHelper";
public static final int TYPE_SHOW = 1;
public static final int TYPE_KEYBOARD_INPUT = 2;
@@ -99,10 +108,19 @@
* Called when the command finishes execution.
*/
private void scheduleNextTask(CommandInfo command) {
- if (!mPendingCommands.isEmpty() && mPendingCommands.get(0) == command) {
- mPendingCommands.remove(0);
- executeNext();
+ if (mPendingCommands.isEmpty()) {
+ Log.d(TAG, "no pending commands to schedule");
+ return;
}
+ if (mPendingCommands.get(0) != command) {
+ Log.d(TAG, "next task not scheduled."
+ + " mPendingCommands[0] type is " + mPendingCommands.get(0)
+ + " - command type is: " + command);
+ return;
+ }
+ Log.d(TAG, "scheduleNextTask called: " + command);
+ mPendingCommands.remove(0);
+ executeNext();
}
/**
@@ -113,10 +131,14 @@
@UiThread
private void executeNext() {
if (mPendingCommands.isEmpty()) {
+ Log.d(TAG, "executeNext - mPendingCommands is empty");
return;
}
CommandInfo cmd = mPendingCommands.get(0);
- if (executeCommand(cmd)) {
+
+ boolean result = executeCommand(cmd);
+ Log.d(TAG, "executeNext cmd type: " + cmd + ", result: " + result);
+ if (result) {
scheduleNextTask(cmd);
}
}
@@ -138,14 +160,18 @@
@BinderThread
public void addCommand(int type) {
if (mPendingCommands.size() >= MAX_QUEUE_SIZE) {
+ Log.d(TAG, "the pending command queue is full (" + mPendingCommands.size() + "). "
+ + "command not added: " + type);
return;
}
+ Log.d(TAG, "adding command type: " + type);
CommandInfo cmd = new CommandInfo(type);
MAIN_EXECUTOR.execute(() -> addCommand(cmd));
}
@UiThread
public void clearPendingCommands() {
+ Log.d(TAG, "clearing pending commands - size: " + mPendingCommands.size());
mPendingCommands.clear();
}
@@ -170,15 +196,17 @@
RunnableList callbackList = null;
if (taskView != null) {
mWaitForToggleCommandComplete = true;
- taskView.setEndQuickswitchCuj(true);
+ taskView.setEndQuickSwitchCuj(true);
callbackList = taskView.launchTasks();
}
if (callbackList != null) {
callbackList.add(() -> {
+ Log.d(TAG, "launching task callback: " + cmd);
scheduleNextTask(cmd);
mWaitForToggleCommandComplete = false;
});
+ Log.d(TAG, "launching task - waiting for callback: " + cmd);
return false;
} else {
recents.startHome();
@@ -191,16 +219,23 @@
* Executes the task and returns true if next task can be executed. If false, then the next
* task is deferred until {@link #scheduleNextTask} is called
*/
- private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
+ private <T extends StatefulActivity<?> & RecentsViewContainer> boolean executeCommand(
+ CommandInfo cmd) {
if (mWaitForToggleCommandComplete && cmd.type == TYPE_TOGGLE) {
+ Log.d(TAG, "executeCommand: " + cmd
+ + " - waiting for toggle command complete");
return true;
}
BaseActivityInterface<?, T> activityInterface =
mOverviewComponentObserver.getActivityInterface();
- RecentsView visibleRecentsView = activityInterface.getVisibleRecentsView();
- RecentsView createdRecentsView;
+
+ RecentsView<?, ?> visibleRecentsView = activityInterface.getVisibleRecentsView();
+ RecentsView<?, ?> createdRecentsView;
+
+ Log.d(TAG, "executeCommand: " + cmd
+ + " - visibleRecentsView: " + visibleRecentsView);
if (visibleRecentsView == null) {
- T activity = activityInterface.getCreatedActivity();
+ T activity = activityInterface.getCreatedContainer();
createdRecentsView = activity == null ? null : activity.getOverviewPanel();
DeviceProfile dp = activity == null ? null : activity.getDeviceProfile();
TaskbarUIController uiController = activityInterface.getTaskbarController();
@@ -283,20 +318,24 @@
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
updateRecentsViewFocus(cmd);
+ logShowOverviewFrom(cmd.type);
}
+
@Override
public void onAnimationEnd(Animator animation) {
+ Log.d(TAG, "switching to Overview state - onAnimationEnd: " + cmd);
super.onAnimationEnd(animation);
onRecentsViewFocusUpdated(cmd);
scheduleNextTask(cmd);
}
};
if (activityInterface.switchToRecentsIfVisible(animatorListener)) {
+ Log.d(TAG, "switching to Overview state - waiting: " + cmd);
// If successfully switched, wait until animation finishes
return false;
}
- final T activity = activityInterface.getCreatedActivity();
+ final T activity = activityInterface.getCreatedContainer();
if (activity != null) {
InteractionJankMonitorWrapper.begin(
activity.getRootView(),
@@ -310,13 +349,14 @@
.newHandler(gestureState, cmd.createTime);
interactionHandler.setGestureEndCallback(
() -> onTransitionComplete(cmd, interactionHandler));
- interactionHandler.initWhenReady();
+ interactionHandler.initWhenReady("OverviewCommandHelper: cmd.type=" + cmd.type);
RecentsAnimationListener recentAnimListener = new RecentsAnimationListener() {
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
updateRecentsViewFocus(cmd);
+ logShowOverviewFrom(cmd.type);
activityInterface.runOnInitBackgroundStateUI(() ->
interactionHandler.onGestureEnded(0, new PointF()));
cmd.removeListener(this);
@@ -327,7 +367,7 @@
interactionHandler.onGestureCancelled();
cmd.removeListener(this);
- T createdActivity = activityInterface.getCreatedActivity();
+ T createdActivity = activityInterface.getCreatedContainer();
if (createdActivity == null) {
return;
}
@@ -357,10 +397,12 @@
cmd.mActiveCallbacks.addListener(recentAnimListener);
}
Trace.beginAsyncSection(TRANSITION_NAME, 0);
+ Log.d(TAG, "switching via recents animation - onGestureStarted: " + cmd);
return false;
}
private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) {
+ Log.d(TAG, "switching via recents animation - onTransitionComplete: " + cmd);
cmd.removeListener(handler);
Trace.endAsyncSection(TRANSITION_NAME, 0);
onRecentsViewFocusUpdated(cmd);
@@ -418,6 +460,33 @@
return true;
}
+ private <T extends StatefulActivity<?> & RecentsViewContainer>
+ void logShowOverviewFrom(int cmdType) {
+ BaseActivityInterface<?, T> activityInterface =
+ mOverviewComponentObserver.getActivityInterface();
+ var container = activityInterface.getCreatedContainer();
+ if (container != null) {
+ StatsLogManager.LauncherEvent event;
+ switch (cmdType) {
+ case TYPE_SHOW -> event = LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT;
+ case TYPE_HIDE ->
+ event = LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH;
+ case TYPE_TOGGLE -> event = LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON;
+ default -> {
+ return;
+ }
+ }
+
+ StatsLogManager.newInstance(container.asContext())
+ .logger()
+ .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setTaskSwitcherContainer(
+ LauncherAtom.TaskSwitcherContainer.getDefaultInstance())
+ .build())
+ .log(event);
+ }
+ }
+
public void dump(PrintWriter pw) {
pw.println("OverviewCommandHelper:");
pw.println(" mPendingCommands=" + mPendingCommands.size());
@@ -442,5 +511,15 @@
mActiveCallbacks.removeListener(listener);
}
}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "CommandInfo("
+ + "type=" + type + ", "
+ + "createTime=" + createTime + ", "
+ + "mActiveCallbacks=" + mActiveCallbacks
+ + ")";
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0a02e99..a71e314 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -109,6 +109,8 @@
* Sets a listener for changes in {@link #isHomeAndOverviewSame()}
*/
public void setOverviewChangeListener(Consumer<Boolean> overviewChangeListener) {
+ // TODO(b/337861962): This method should be able to support multiple listeners instead of
+ // one so that we can reuse the same instance of this class across multiple places
mOverviewChangeListener = overviewChangeListener;
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index c3a4351..b4b8c5b 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -198,6 +198,12 @@
.unstashBubbleBarIfStashed();
});
return response;
+ case TestProtocol.REQUEST_INJECT_FAKE_TRACKPAD:
+ runOnTISBinder(tisBinder -> tisBinder.injectFakeTrackpadForTesting());
+ return response;
+ case TestProtocol.REQUEST_EJECT_FAKE_TRACKPAD:
+ runOnTISBinder(tisBinder -> tisBinder.ejectFakeTrackpadForTesting());
+ return response;
}
return super.call(method, arg, extras);
@@ -208,7 +214,7 @@
RecentsAnimationDeviceState rads = new RecentsAnimationDeviceState(mContext);
OverviewComponentObserver observer = new OverviewComponentObserver(mContext, rads);
try {
- return observer.getActivityInterface().getCreatedActivity();
+ return observer.getActivityInterface().getCreatedContainer();
} finally {
observer.onDestroy();
rads.destroy();
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index f3704e09..b08a46f 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -20,7 +20,7 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
import android.app.ActivityManager;
@@ -31,6 +31,7 @@
import android.os.RemoteException;
import android.util.SparseBooleanArray;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.util.LooperExecutor;
@@ -44,6 +45,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -73,7 +75,7 @@
private ArrayList<ActivityManager.RunningTaskInfo> mRunningTasks;
public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager,
- SystemUiProxy sysUiProxy) {
+ SystemUiProxy sysUiProxy, TopTaskTracker topTaskTracker) {
mMainThreadExecutor = mainThreadExecutor;
mKeyguardManager = keyguardManager;
mChangeId = 1;
@@ -97,6 +99,20 @@
RecentTasksList.this.onRunningTaskVanished(taskInfo);
});
}
+
+ @Override
+ public void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ mMainThreadExecutor.execute(() -> {
+ RecentTasksList.this.onRunningTaskChanged(taskInfo);
+ });
+ }
+
+ @Override
+ public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+ mMainThreadExecutor.execute(() -> {
+ topTaskTracker.onTaskMovedToFront(taskInfo);
+ });
+ }
});
// We may receive onRunningTaskAppeared events later for tasks which have already been
// included in the list returned by mSysUiProxy.getRunningTasks(), or may receive
@@ -126,11 +142,11 @@
* Asynchronously fetches the list of recent tasks, reusing cached list if available.
*
* @param loadKeysOnly Whether to load other associated task data, or just the key
- * @param callback The callback to receive the list of recent tasks
+ * @param callback The callback to receive the list of recent tasks
* @return The change id of the current task list
*/
public synchronized int getTasks(boolean loadKeysOnly,
- Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) {
+ @Nullable Consumer<List<GroupTask>> callback, Predicate<GroupTask> filter) {
final int requestLoadId = mChangeId;
if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
// The list is up to date, send the callback on the next frame,
@@ -191,7 +207,7 @@
mChangeId++;
}
- /**
+ /**
* Registers a listener for running tasks
*/
public void registerRunningTasksListener(RecentsModel.RunningTasksListener listener) {
@@ -244,6 +260,20 @@
}
}
+ private void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ // Find the task from the list of running tasks, if it exists
+ for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) {
+ if (existingTask.taskId != taskInfo.taskId) continue;
+
+ mRunningTasks.remove(existingTask);
+ mRunningTasks.add(taskInfo);
+ if (mRunningTasksListener != null) {
+ mRunningTasksListener.onRunningTasksChanged();
+ }
+ return;
+ }
+ }
+
/**
* Loads and creates a list of all the recent tasks.
*/
@@ -270,9 +300,13 @@
int numVisibleTasks = 0;
for (GroupedRecentTaskInfo rawTask : rawTasks) {
- if (isDesktopModeSupported() && rawTask.getType() == TYPE_FREEFORM) {
- GroupTask desktopTask = createDesktopTask(rawTask);
- allTasks.add(desktopTask);
+ if (rawTask.getType() == TYPE_FREEFORM) {
+ // TYPE_FREEFORM tasks is only created when enableDesktopWindowingMode() is true,
+ // leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
+ if (enableDesktopWindowingMode()) {
+ GroupTask desktopTask = createDesktopTask(rawTask);
+ allTasks.add(desktopTask);
+ }
continue;
}
ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1();
@@ -386,4 +420,4 @@
return mRequestId == requestId && (!mKeysOnly || loadKeysOnly);
}
}
-}
\ No newline at end of file
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 02f9a69..d7f7a07 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -27,7 +27,7 @@
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -57,6 +57,7 @@
import com.android.launcher3.LauncherAnimationRunner;
import com.android.launcher3.LauncherAnimationRunner.AnimationResult;
import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
+import com.android.launcher3.LauncherRootView;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
@@ -85,6 +86,7 @@
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
import java.io.FileDescriptor;
@@ -95,7 +97,8 @@
* A recents activity that shows the recently launched tasks as swipable task cards.
* See {@link com.android.quickstep.views.RecentsView}.
*/
-public final class RecentsActivity extends StatefulActivity<RecentsState> {
+public final class RecentsActivity extends StatefulActivity<RecentsState> implements
+ RecentsViewContainer {
private static final String TAG = "RecentsActivity";
public static final ActivityTracker<RecentsActivity> ACTIVITY_TRACKER =
@@ -109,11 +112,11 @@
private RecentsDragLayer mDragLayer;
private ScrimView mScrimView;
private FallbackRecentsView mFallbackRecentsView;
- private OverviewActionsView mActionsView;
+ private OverviewActionsView<?> mActionsView;
private TISBindHelper mTISBindHelper;
private @Nullable FallbackTaskbarUIController mTaskbarUIController;
- private StateManager<RecentsState> mStateManager;
+ private StateManager<RecentsState, RecentsActivity> mStateManager;
// Strong refs to runners which are cleared when the activity is destroyed
private RemoteAnimationFactory mActivityLaunchAnimationRunner;
@@ -130,21 +133,22 @@
* Init drag layer and overview panel views.
*/
protected void setupViews() {
- inflateRootView(R.layout.fallback_recents_activity);
- setContentView(getRootView());
- mDragLayer = findViewById(R.id.drag_layer);
- mScrimView = findViewById(R.id.scrim_view);
- mFallbackRecentsView = findViewById(R.id.overview_panel);
- mActionsView = findViewById(R.id.overview_actions_view);
- getRootView().getSysUiScrim().getSysUIProgress().updateValue(0);
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
+ // SplitSelectStateController needs to be created before setContentView()
mSplitSelectStateController =
new SplitSelectStateController(this, mHandler, getStateManager(),
null /* depthController */, getStatsLogManager(),
systemUiProxy, RecentsModel.INSTANCE.get(this),
null /*activityBackCallback*/);
- mDragLayer.recreateControllers();
- if (isDesktopModeSupported()) {
+ // Setup root and child views
+ inflateRootView(R.layout.fallback_recents_activity);
+ LauncherRootView rootView = getRootView();
+ mDragLayer = rootView.findViewById(R.id.drag_layer);
+ mScrimView = rootView.findViewById(R.id.scrim_view);
+ mFallbackRecentsView = rootView.findViewById(R.id.overview_panel);
+ mActionsView = rootView.findViewById(R.id.overview_actions_view);
+
+ if (enableDesktopWindowingMode()) {
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
getStateManager(), systemUiProxy, getIApplicationThread(),
null /* depthController */
@@ -153,6 +157,10 @@
mFallbackRecentsView.init(mActionsView, mSplitSelectStateController,
mDesktopRecentsTransitionController);
+ setContentView(rootView);
+ rootView.getSysUiScrim().getSysUIProgress().updateValue(0);
+ mDragLayer.recreateControllers();
+
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
}
@@ -224,11 +232,12 @@
}
@Override
- public <T extends View> T getOverviewPanel() {
- return (T) mFallbackRecentsView;
+ public FallbackRecentsView getOverviewPanel() {
+ return mFallbackRecentsView;
}
- public OverviewActionsView getActionsView() {
+ @Override
+ public OverviewActionsView<?> getActionsView() {
return mActionsView;
}
@@ -368,6 +377,9 @@
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
ACTIVITY_TRACKER.handleCreate(this);
+
+ // Set screen title for Talkback
+ setTitle(R.string.accessibility_recent_apps);
}
@Override
@@ -380,6 +392,11 @@
}
}
+ @Override
+ public boolean shouldAnimateStateChange() {
+ return false;
+ }
+
/**
* Initialize/update the device profile.
*/
@@ -456,12 +473,12 @@
};
@Override
- protected void collectStateHandlers(List<StateHandler> out) {
+ public void collectStateHandlers(List<StateHandler<RecentsState>> out) {
out.add(new FallbackRecentsStateController(this));
}
@Override
- public StateManager<RecentsState> getStateManager() {
+ public StateManager<RecentsState, RecentsActivity> getStateManager() {
return mStateManager;
}
@@ -503,4 +520,9 @@
public TISBindHelper getTISBindHelper() {
return mTISBindHelper;
}
+
+ @Override
+ public boolean isRecentsViewVisible() {
+ return getStateManager().getState().isRecentsViewVisible();
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 5d26ec0..da7a98f 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -39,6 +39,7 @@
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -219,6 +220,13 @@
}
}
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "RecentsAnimationCallbacks:");
+
+ pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
+ pw.println(prefix + "\tmCancelled=" + mCancelled);
+ }
+
/**
* Listener for the recents animation callbacks.
*/
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 06a442b..1b05e28 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -41,6 +41,7 @@
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import java.io.PrintWriter;
import java.util.function.Consumer;
/**
@@ -267,4 +268,14 @@
public boolean getFinishTargetIsLauncher() {
return mFinishTargetIsLauncher;
}
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "RecentsAnimationController:");
+
+ pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
+ pw.println(prefix + "\tmUseLauncherSysBarFlags=" + mUseLauncherSysBarFlags);
+ pw.println(prefix + "\tmSplitScreenMinimized=" + mSplitScreenMinimized);
+ pw.println(prefix + "\tmFinishRequested=" + mFinishRequested);
+ pw.println(prefix + "\tmFinishTargetIsLauncher=" + mFinishTargetIsLauncher);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index c56a621..7adce74 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -33,6 +33,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_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_MAGNIFICATION_OVERLAP;
@@ -44,6 +45,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED;
import android.app.ActivityTaskManager;
import android.content.Context;
@@ -60,7 +62,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
@@ -68,6 +69,7 @@
import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.AssistStateManager;
import com.android.quickstep.util.GestureExclusionManager;
import com.android.quickstep.util.GestureExclusionManager.ExclusionListener;
import com.android.quickstep.util.NavBarPosition;
@@ -97,7 +99,7 @@
private final DisplayController mDisplayController;
private final GestureExclusionManager mExclusionManager;
-
+ private final AssistStateManager mAssistStateManager;
private final RotationTouchHelper mRotationTouchHelper;
private final TaskStackChangeListener mPipListener;
@@ -107,7 +109,7 @@
private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
- private @SystemUiStateFlags int mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
+ private @SystemUiStateFlags long mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
private NavigationMode mMode = THREE_BUTTONS;
private NavBarPosition mNavBarPosition;
@@ -148,6 +150,7 @@
mContext = context;
mDisplayController = DisplayController.INSTANCE.get(context);
mExclusionManager = exclusionManager;
+ mAssistStateManager = AssistStateManager.INSTANCE.get(context);
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
if (isInstanceForTouches) {
@@ -350,7 +353,7 @@
/**
* Updates the system ui state flags from SystemUI.
*/
- public void setSystemUiFlags(int stateFlags) {
+ public void setSystemUiFlags(@SystemUiStateFlags long stateFlags) {
mSystemUiStateFlags = stateFlags;
}
@@ -358,7 +361,8 @@
* @return the system ui state flags.
*/
// TODO(141886704): See if we can remove this
- public int getSystemUiStateFlags() {
+ @SystemUiStateFlags
+ public long getSystemUiStateFlags() {
return mSystemUiStateFlags;
}
@@ -383,7 +387,7 @@
boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
|| (mSystemUiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
|| mRotationTouchHelper.isTaskListFrozen();
- return canStartWithNavHidden && canStartTrackpadGesture();
+ return canStartWithNavHidden && canStartAnyGesture();
}
/**
@@ -392,13 +396,24 @@
* mode.
*/
public boolean canStartTrackpadGesture() {
- return (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
- && (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) == 0
- && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
- && (mSystemUiStateFlags & SYSUI_STATE_MAGNIFICATION_OVERLAP) == 0
- && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
- || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0)
- && (mSystemUiStateFlags & SYSUI_STATE_DEVICE_DREAMING) == 0;
+ boolean trackpadGesturesEnabled =
+ (mSystemUiStateFlags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) == 0;
+ return trackpadGesturesEnabled && canStartAnyGesture();
+ }
+
+ /**
+ * Common logic to determine if either trackpad or finger gesture can be started
+ */
+ private boolean canStartAnyGesture() {
+ boolean homeOrOverviewEnabled = (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
+ || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
+ long gestureDisablingStates = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+ | SYSUI_STATE_QUICK_SETTINGS_EXPANDED
+ | SYSUI_STATE_MAGNIFICATION_OVERLAP
+ | SYSUI_STATE_DEVICE_DREAMING
+ | SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
+ return (gestureDisablingStates & mSystemUiStateFlags) == 0 && homeOrOverviewEnabled;
}
/**
@@ -588,9 +603,8 @@
: QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
- float customSlopMultiplier =
- FeatureFlags.LPNH_SLOP_PERCENTAGE.get() / 100f;
+ if (mAssistStateManager.getLPNHCustomSlopMultiplier().isPresent()) {
+ float customSlopMultiplier = mAssistStateManager.getLPNHCustomSlopMultiplier().get();
return customSlopMultiplier * slopMultiplier * touchSlop;
} else {
return slopMultiplier * touchSlop;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index 556dd7e..82bb453 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -17,13 +17,16 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+
+import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
+import java.io.PrintWriter;
+
/**
* Extension of {@link RemoteAnimationTargets} with additional information about swipe
* up animation
@@ -52,7 +55,7 @@
* @return {@code true} if at least one target app is a desktop task
*/
public boolean hasDesktopTasks() {
- if (!isDesktopModeSupported()) {
+ if (!enableDesktopWindowingMode()) {
return false;
}
for (RemoteAnimationTarget target : apps) {
@@ -62,4 +65,14 @@
}
return false;
}
+
+ @Override
+ public void dump(String prefix, PrintWriter pw) {
+ super.dump(prefix, pw);
+ prefix += '\t';
+ pw.println(prefix + "RecentsAnimationTargets:");
+
+ pw.println(prefix + "\thomeContentInsets=" + homeContentInsets);
+ pw.println(prefix + "\tminimizedHomeBounds=" + minimizedHomeBounds);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 89351aa..6eefe4a 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -33,6 +33,7 @@
import android.os.Process;
import android.os.UserHandle;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.icons.IconProvider;
@@ -40,6 +41,7 @@
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
+import com.android.quickstep.recents.data.RecentTasksDataSource;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.systemui.shared.recents.model.Task;
@@ -60,8 +62,8 @@
* Singleton class to load and manage recents model.
*/
@TargetApi(Build.VERSION_CODES.O)
-public class RecentsModel implements IconChangeListener, TaskStackChangeListener,
- TaskVisualsChangeListener, SafeCloseable {
+public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
+ TaskStackChangeListener, TaskVisualsChangeListener, SafeCloseable {
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
@@ -88,7 +90,8 @@
this(context,
new RecentTasksList(MAIN_EXECUTOR,
context.getSystemService(KeyguardManager.class),
- SystemUiProxy.INSTANCE.get(context)),
+ SystemUiProxy.INSTANCE.get(context),
+ TopTaskTracker.INSTANCE.get(context)),
new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider),
new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
iconProvider,
@@ -141,7 +144,8 @@
* always called on the UI thread.
* @return the request id associated with this call.
*/
- public int getTasks(Consumer<ArrayList<GroupTask>> callback) {
+ @Override
+ public int getTasks(@Nullable Consumer<List<GroupTask>> callback) {
return mTaskList.getTasks(false /* loadKeysOnly */, callback,
RecentsFilterState.DEFAULT_FILTER);
}
@@ -155,7 +159,7 @@
* callback.
* @return the request id associated with this call.
*/
- public int getTasks(Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) {
+ public int getTasks(@Nullable Consumer<List<GroupTask>> callback, Predicate<GroupTask> filter) {
return mTaskList.getTasks(false /* loadKeysOnly */, callback, filter);
}
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index e0c7403..57edd82 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -22,6 +22,7 @@
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -149,6 +150,14 @@
}
}
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "RemoteAnimationTargets:");
+
+ pw.println(prefix + "\ttargetMode=" + targetMode);
+ pw.println(prefix + "\thasRecents=" + hasRecents);
+ pw.println(prefix + "\tmReleased=" + mReleased);
+ }
+
/**
* Interface for intercepting surface release method
*/
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 6a9caf7..3dec381 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -17,7 +17,6 @@
package com.android.quickstep;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
import android.app.WindowConfiguration;
@@ -58,7 +57,7 @@
/**
* Use this constructor if remote targets are split-screen independent
*/
- public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy,
+ public RemoteTargetGluer(Context context, BaseContainerInterface sizingStrategy,
RemoteAnimationTargets targets, boolean forDesktop) {
init(context, sizingStrategy, targets.apps.length, forDesktop);
}
@@ -67,18 +66,16 @@
* Use this constructor if you want the number of handles created to match the number of active
* running tasks
*/
- public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
- if (isDesktopModeSupported()) {
- DesktopVisibilityController desktopVisibilityController =
- LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
- if (desktopVisibilityController != null) {
- int visibleTasksCount = desktopVisibilityController.getVisibleFreeformTasksCount();
- if (visibleTasksCount > 0) {
- // Allocate +1 to account for a new task added to the desktop mode
- int numHandles = visibleTasksCount + 1;
- init(context, sizingStrategy, numHandles, true /* forDesktop */);
- return;
- }
+ public RemoteTargetGluer(Context context, BaseContainerInterface sizingStrategy) {
+ DesktopVisibilityController desktopVisibilityController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ int visibleTasksCount = desktopVisibilityController.getVisibleDesktopTasksCount();
+ if (visibleTasksCount > 0) {
+ // Allocate +1 to account for a new task added to the desktop mode
+ int numHandles = visibleTasksCount + 1;
+ init(context, sizingStrategy, numHandles, true /* forDesktop */);
+ return;
}
}
@@ -87,13 +84,13 @@
init(context, sizingStrategy, DEFAULT_NUM_HANDLES, false /* forDesktop */);
}
- private void init(Context context, BaseActivityInterface sizingStrategy, int numHandles,
+ private void init(Context context, BaseContainerInterface sizingStrategy, int numHandles,
boolean forDesktop) {
mRemoteTargetHandles = createHandles(context, sizingStrategy, numHandles, forDesktop);
}
private RemoteTargetHandle[] createHandles(Context context,
- BaseActivityInterface sizingStrategy, int numHandles, boolean forDesktop) {
+ BaseContainerInterface sizingStrategy, int numHandles, boolean forDesktop) {
RemoteTargetHandle[] handles = new RemoteTargetHandle[numHandles];
for (int i = 0; i < numHandles; i++) {
TaskViewSimulator tvs = new TaskViewSimulator(context, sizingStrategy);
@@ -225,8 +222,10 @@
for (int i = 0; i < mRemoteTargetHandles.length; i++) {
RemoteAnimationTarget primaryTaskTarget = targets.apps[i];
+ List<RemoteAnimationTarget> excludeTargets = Arrays.stream(targets.apps)
+ .filter(target -> target.taskId != primaryTaskTarget.taskId).toList();
mRemoteTargetHandles[i].mTransformParams.setTargetSet(
- createRemoteAnimationTargetsForTaskId(targets, primaryTaskTarget.taskId));
+ createRemoteAnimationTargetsForTarget(targets, excludeTargets));
mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
}
return mRemoteTargetHandles;
@@ -293,33 +292,6 @@
}
/**
- * Ensures that we only animate one specific app target. Includes ancillary targets such as
- * home/recents
- *
- * @param targets remote animation targets to filter
- * @param taskId id for a task that we want this remote animation to apply to
- * @return {@link RemoteAnimationTargets} where app target only includes the app that has the
- * {@code taskId} that was passed in
- */
- private RemoteAnimationTargets createRemoteAnimationTargetsForTaskId(
- RemoteAnimationTargets targets, int taskId) {
- RemoteAnimationTarget[] targetApp = null;
- for (RemoteAnimationTarget targetCompat : targets.unfilteredApps) {
- if (targetCompat.taskId == taskId) {
- targetApp = new RemoteAnimationTarget[]{targetCompat};
- break;
- }
- }
-
- if (targetApp == null) {
- targetApp = new RemoteAnimationTarget[0];
- }
-
- return new RemoteAnimationTargets(targetApp, targets.wallpapers, targets.nonApps,
- targets.targetMode);
- }
-
- /**
* The object returned by this is may be modified in
* {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}, specifically the length of the
* array may be shortened based on the number of RemoteAnimationTargets present.
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 2d47097..6f1ab7d 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -39,6 +39,7 @@
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.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -50,7 +51,7 @@
/**
* Helper class for transforming touch events
*/
-public class RotationTouchHelper implements DisplayInfoChangeListener {
+public class RotationTouchHelper implements DisplayInfoChangeListener, SafeCloseable {
public static final MainThreadInitializedObject<RotationTouchHelper> INSTANCE =
new MainThreadInitializedObject<>(RotationTouchHelper::new);
@@ -156,7 +157,7 @@
// Register for navigation mode changes
mDisplayController.addChangeListener(this);
DisplayController.Info info = mDisplayController.getInfo();
- onDisplayInfoChangedInternal(info, CHANGE_ALL, info.navigationMode.hasGestures);
+ onDisplayInfoChangedInternal(info, CHANGE_ALL, info.getNavigationMode().hasGestures);
runOnDestroy(() -> mDisplayController.removeChangeListener(this));
mOrientationListener = new OrientationEventListener(mContext) {
@@ -197,6 +198,11 @@
mOnDestroyActions.add(action);
}
+ @Override
+ public void close() {
+ destroy();
+ }
+
/**
* Cleans up all the registered listeners and receivers.
*/
@@ -285,7 +291,7 @@
}
if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
- NavigationMode newMode = info.navigationMode;
+ NavigationMode newMode = info.getNavigationMode();
mOrientationTouchTransformer.setNavigationMode(newMode, mDisplayController.getInfo(),
mContext.getResources());
@@ -345,14 +351,14 @@
}
void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
- BaseActivityInterface activityInterface) {
+ BaseContainerInterface containerInterface) {
if (endTarget == GestureState.GestureEndTarget.RECENTS) {
mInOverview = true;
if (!mTaskListFrozen) {
// If we're in landscape w/o ever quickswitching, show the navbar in landscape
enableMultipleRegions(true);
}
- activityInterface.onExitOverview(this, mExitOverviewRunnable);
+ containerInterface.onExitOverview(this, mExitOverviewRunnable);
} else if (endTarget == GestureState.GestureEndTarget.HOME
|| endTarget == GestureState.GestureEndTarget.ALL_APPS) {
enableMultipleRegions(false);
diff --git a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
index f474796..29a57fc 100644
--- a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
@@ -24,22 +24,30 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
public class SimpleOrientationTouchTransformer implements
- DisplayController.DisplayInfoChangeListener {
+ DisplayController.DisplayInfoChangeListener, SafeCloseable {
public static final MainThreadInitializedObject<SimpleOrientationTouchTransformer> INSTANCE =
new MainThreadInitializedObject<>(SimpleOrientationTouchTransformer::new);
+ private final Context mContext;
private OrientationRectF mOrientationRectF;
public SimpleOrientationTouchTransformer(Context context) {
+ mContext = context;
DisplayController.INSTANCE.get(context).addChangeListener(this);
onDisplayInfoChanged(context, DisplayController.INSTANCE.get(context).getInfo(),
CHANGE_ALL);
}
@Override
+ public void close() {
+ DisplayController.INSTANCE.get(mContext).removeChangeListener(this);
+ }
+
+ @Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
if ((flags & (CHANGE_ROTATION | CHANGE_ACTIVE_SCREEN)) == 0) {
return;
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index b920c10..ba33c62 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -17,6 +17,8 @@
import static com.android.app.animation.Interpolators.ACCELERATE_1_5;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
+import static com.android.launcher3.PagedView.INVALID_PAGE;
import android.animation.Animator;
import android.content.Context;
@@ -27,6 +29,7 @@
import android.view.RemoteAnimationTarget;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
@@ -36,6 +39,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.views.ClipIconView;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -46,6 +50,8 @@
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
import java.util.Arrays;
import java.util.function.Consumer;
@@ -86,7 +92,7 @@
updateIsGestureForSplit(TopTaskTracker.INSTANCE.get(context)
.getRunningSplitTaskIds().length);
- mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
+ mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getContainerInterface());
mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
runActionOnRemoteHandles(remoteTargetHandle ->
remoteTargetHandle.getTaskViewSimulator().getOrientationState().update(
@@ -97,9 +103,10 @@
protected void initTransitionEndpoints(DeviceProfile dp) {
mDp = dp;
- mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
- dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].getTaskViewSimulator()
- .getOrientationState().getOrientationHandler());
+ mTransitionDragLength = mGestureState.getContainerInterface()
+ .getSwipeUpDestinationAndLength(dp, mContext, TEMP_RECT,
+ mRemoteTargetHandles[0].getTaskViewSimulator().getOrientationState()
+ .getOrientationHandler());
mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
@@ -200,7 +207,7 @@
public void setAnimation(RectFSpringAnim anim) { }
- public void update(RectF currentRect, float progress, float radius) { }
+ public void update(RectF currentRect, float progress, float radius, int overlayAlpha) { }
public void onCancel() { }
@@ -221,6 +228,33 @@
}
return Utilities.mapToRange(progress, start, end, 1, 0, ACCELERATE_1_5);
}
+
+ /**
+ * Sets a {@link com.android.launcher3.views.ClipIconView.TaskViewArtist} that should be
+ * used draw a {@link TaskView} during this home animation.
+ */
+ public void setTaskViewArtist(ClipIconView.TaskViewArtist taskViewArtist) { }
+
+ public boolean isAnimationReady() {
+ return true;
+ }
+
+ public boolean isAnimatingIntoIcon() {
+ return false;
+ }
+
+ @Nullable
+ public TaskView getTargetTaskView() {
+ return null;
+ }
+
+ public boolean isRtl() {
+ return Utilities.isRtl(mContext.getResources());
+ }
+
+ public boolean isPortrait() {
+ return !mDp.isLandscape && !mDp.isSeascape();
+ }
}
/**
@@ -275,9 +309,13 @@
for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
i < mRemoteTargetHandlesLength; i++) {
RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
- out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory,
- targetRect, remoteHandle.getTransformParams(),
- remoteHandle.getTaskViewSimulator(), startRects[i], homeToWindowPositionMap[i]);
+ out[i] = getWindowAnimationToHomeInternal(
+ homeAnimationFactory,
+ targetRect,
+ remoteHandle.getTransformParams(),
+ remoteHandle.getTaskViewSimulator(),
+ startRects[i],
+ homeToWindowPositionMap[i]);
}
return out;
}
@@ -287,21 +325,39 @@
}
private RectFSpringAnim getWindowAnimationToHomeInternal(
- HomeAnimationFactory homeAnimationFactory, RectF targetRect,
- TransformParams transformParams, TaskViewSimulator taskViewSimulator,
- RectF startRect, Matrix homeToWindowPositionMap) {
+ HomeAnimationFactory homeAnimationFactory,
+ RectF targetRect,
+ TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator,
+ RectF startRect,
+ Matrix homeToWindowPositionMap) {
RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect());
// Move the startRect to Launcher space as floatingIconView runs in Launcher
Matrix windowToHomePositionMap = new Matrix();
- // If the start rect ends up overshooting too much to the left/right offscreen, bring it
- // back to fullscreen. This can happen when the recentsScroll value isn't aligned with
- // the pageScroll value for a given taskView, see b/228829958#comment12
- mRemoteTargetHandles[0].getTaskViewSimulator().getOrientationState().getOrientationHandler()
- .fixBoundsForHomeAnimStartRect(startRect, mDp);
+ TaskView targetTaskView = homeAnimationFactory.getTargetTaskView();
+ if (targetTaskView == null) {
+ // If the start rect ends up overshooting too much to the left/right offscreen, bring it
+ // back to fullscreen. This can happen when the recentsScroll value isn't aligned with
+ // the pageScroll value for a given taskView, see b/228829958#comment12
+ mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState()
+ .getOrientationHandler()
+ .fixBoundsForHomeAnimStartRect(startRect, mDp);
+ }
homeToWindowPositionMap.invert(windowToHomePositionMap);
windowToHomePositionMap.mapRect(startRect);
+ RectF invariantStartRect = new RectF(startRect);
+
+ if (targetTaskView != null) {
+ Rect thumbnailBounds = new Rect();
+ targetTaskView.getThumbnailBounds(thumbnailBounds, /* relativeToDragLayer= */ true);
+
+ invariantStartRect = new RectF(thumbnailBounds);
+ invariantStartRect.offsetTo(startRect.left, thumbnailBounds.top);
+ startRect = new RectF(thumbnailBounds);
+ }
boolean useTaskbarHotseatParams = mDp.isTaskbarPresent
&& homeAnimationFactory.isInHotseat();
@@ -311,8 +367,12 @@
homeAnimationFactory.setAnimation(anim);
SpringAnimationRunner runner = new SpringAnimationRunner(
- homeAnimationFactory, cropRectF, homeToWindowPositionMap,
- transformParams, taskViewSimulator);
+ homeAnimationFactory,
+ cropRectF,
+ homeToWindowPositionMap,
+ transformParams,
+ taskViewSimulator,
+ invariantStartRect);
anim.addAnimatorListener(runner);
anim.addOnUpdateListener(runner);
return anim;
@@ -335,9 +395,30 @@
final float mStartRadius;
final float mEndRadius;
- SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
- Matrix homeToWindowPositionMap, TransformParams transformParams,
- TaskViewSimulator taskViewSimulator) {
+ final RectF mRunningTaskViewStartRectF;
+ @Nullable
+ final TaskView mTargetTaskView;
+ final float mRunningTaskViewScrollOffset;
+ final float mTaskViewWidth;
+ final float mTaskViewHeight;
+ final boolean mIsPortrait;
+ final Rect mThumbnailStartBounds = new Rect();
+
+ // Store the mTargetTaskView view properties onAnimationStart so that we can reset them
+ // when cleaning up.
+ float mTaskViewAlpha;
+ float mTaskViewTranslationX;
+ float mTaskViewTranslationY;
+ float mTaskViewScaleX;
+ float mTaskViewScaleY;
+
+ SpringAnimationRunner(
+ HomeAnimationFactory factory,
+ RectF cropRectF,
+ Matrix homeToWindowPositionMap,
+ TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator,
+ RectF invariantStartRect) {
mAnimationFactory = factory;
mHomeAnim = factory.createActivityAnimationToHome();
mCropRectF = cropRectF;
@@ -350,22 +431,67 @@
// rounding at the end of the animation.
mStartRadius = taskViewSimulator.getCurrentCornerRadius();
mEndRadius = factory.getEndRadius(cropRectF);
+
+ mRunningTaskViewStartRectF = invariantStartRect;
+ mTargetTaskView = factory.getTargetTaskView();
+ mTaskViewWidth = mTargetTaskView == null ? 0 : mTargetTaskView.getWidth();
+ mTaskViewHeight = mTargetTaskView == null ? 0 : mTargetTaskView.getHeight();
+ mIsPortrait = factory.isPortrait();
+ // Use the running task's start position to determine how much it needs to be offset
+ // to end up offscreen.
+ mRunningTaskViewScrollOffset = factory.isRtl()
+ ? (Math.min(0, -invariantStartRect.right))
+ : (Math.max(0, mDp.widthPx - invariantStartRect.left));
}
@Override
public void onUpdate(RectF currentRect, float progress) {
- mHomeAnim.setPlayFraction(progress);
- mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
-
- mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
float alpha = mAnimationFactory.getWindowAlpha(progress);
- mLocalTransformParams
- .setTargetAlpha(alpha)
- .setCornerRadius(cornerRadius);
- mLocalTransformParams.applySurfaceParams(mLocalTransformParams
- .createSurfaceParams(this));
- mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
+
+ mHomeAnim.setPlayFraction(progress);
+ if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) {
+ mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
+ mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
+ mLocalTransformParams
+ .setTargetAlpha(alpha)
+ .setCornerRadius(cornerRadius);
+ } else {
+ mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, mRunningTaskViewStartRectF);
+ mWindowCurrentRect.offset(mRunningTaskViewScrollOffset * progress, 0f);
+ mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
+ mLocalTransformParams.setCornerRadius(mStartRadius);
+ }
+
+ mLocalTransformParams.applySurfaceParams(
+ mLocalTransformParams.createSurfaceParams(this));
+
+ mAnimationFactory.update(
+ currentRect,
+ progress,
+ mMatrix.mapRadius(cornerRadius),
+ !enableAdditionalHomeAnimations() || mTargetTaskView == null
+ ? 0 : (int) (alpha * 255));
+
+ if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) {
+ return;
+ }
+ if (mAnimationFactory.isAnimatingIntoIcon() && mAnimationFactory.isAnimationReady()) {
+ mTargetTaskView.setAlpha(0f);
+ return;
+ }
+ mTargetTaskView.setAlpha(mAnimationFactory.isAnimatingIntoIcon() ? 1f : alpha);
+ float width = mThumbnailStartBounds.width();
+ float height = mThumbnailStartBounds.height();
+ float scale = Math.min(currentRect.width(), currentRect.height())
+ / Math.min(width, height);
+
+ mTargetTaskView.setScaleX(scale);
+ mTargetTaskView.setScaleY(scale);
+ mTargetTaskView.setTranslationX(
+ currentRect.centerX() - mThumbnailStartBounds.centerX());
+ mTargetTaskView.setTranslationY(
+ currentRect.centerY() - mThumbnailStartBounds.centerY());
}
@Override
@@ -378,16 +504,71 @@
@Override
public void onCancel() {
+ cleanUp();
mAnimationFactory.onCancel();
}
@Override
public void onAnimationStart(Animator animation) {
+ setUp();
mHomeAnim.dispatchOnStart();
+ if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) {
+ return;
+ }
+ Rect thumbnailBounds = new Rect();
+ // Use bounds relative to mTargetTaskView since it will be scaled afterwards
+ mTargetTaskView.getThumbnailBounds(thumbnailBounds);
+ mAnimationFactory.setTaskViewArtist(new ClipIconView.TaskViewArtist(
+ mTargetTaskView::draw,
+ 0f,
+ -thumbnailBounds.top,
+ Math.min(mTaskViewHeight, mTaskViewWidth),
+ mIsPortrait));
+ }
+
+ private void setUp() {
+ if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) {
+ return;
+ }
+ RecentsView recentsView = mTargetTaskView.getRecentsView();
+ if (recentsView != null) {
+ recentsView.setOffsetMidpointIndexOverride(
+ recentsView.indexOfChild(mTargetTaskView));
+ }
+ mTargetTaskView.getThumbnailBounds(
+ mThumbnailStartBounds, /* relativeToDragLayer= */ true);
+ mTaskViewAlpha = mTargetTaskView.getAlpha();
+ if (mAnimationFactory.isAnimatingIntoIcon()) {
+ return;
+ }
+ mTaskViewTranslationX = mTargetTaskView.getTranslationX();
+ mTaskViewTranslationY = mTargetTaskView.getTranslationY();
+ mTaskViewScaleX = mTargetTaskView.getScaleX();
+ mTaskViewScaleY = mTargetTaskView.getScaleY();
+ }
+
+ private void cleanUp() {
+ if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) {
+ return;
+ }
+ RecentsView recentsView = mTargetTaskView.getRecentsView();
+ if (recentsView != null) {
+ recentsView.setOffsetMidpointIndexOverride(INVALID_PAGE);
+ }
+ mTargetTaskView.setAlpha(mTaskViewAlpha);
+ if (!mAnimationFactory.isAnimatingIntoIcon()) {
+ mTargetTaskView.setTranslationX(mTaskViewTranslationX);
+ mTargetTaskView.setTranslationY(mTaskViewTranslationY);
+ mTargetTaskView.setScaleX(mTaskViewScaleX);
+ mTargetTaskView.setScaleY(mTaskViewScaleY);
+ return;
+ }
+ mAnimationFactory.setTaskViewArtist(null);
}
@Override
public void onAnimationSuccess(Animator animator) {
+ cleanUp();
mHomeAnim.getAnimationPlayer().end();
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 72f67fc..433baa9 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -23,6 +23,8 @@
import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENT_TASKS_MISSING;
import static com.android.quickstep.util.LogUtils.splitFailureMessage;
+import static com.android.window.flags.Flags.enableDesktopWindowingMode;
+import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -32,7 +34,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.Point;
import android.graphics.Rect;
@@ -63,14 +64,15 @@
import com.android.internal.logging.InstanceId;
import com.android.internal.util.ScreenshotRequest;
import com.android.internal.view.AppearanceRegion;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistUtils;
import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
@@ -82,6 +84,8 @@
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.bubbles.IBubbles;
import com.android.wm.shell.bubbles.IBubblesListener;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
@@ -91,13 +95,12 @@
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.recents.IRecentTasksListener;
+import com.android.wm.shell.shared.IShellTransitions;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
import com.android.wm.shell.splitscreen.ISplitSelectListener;
import com.android.wm.shell.startingsurface.IStartingWindow;
import com.android.wm.shell.startingsurface.IStartingWindowListener;
-import com.android.wm.shell.transition.IHomeTransitionListener;
-import com.android.wm.shell.transition.IShellTransitions;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.io.PrintWriter;
@@ -109,8 +112,8 @@
/**
* Holds the reference to SystemUI.
*/
-public class SystemUiProxy implements ISystemUiProxy, NavHandle {
- private static final String TAG = SystemUiProxy.class.getSimpleName();
+public class SystemUiProxy implements ISystemUiProxy, NavHandle, SafeCloseable {
+ private static final String TAG = "SystemUiProxy";
public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
new MainThreadInitializedObject<>(SystemUiProxy::new);
@@ -157,7 +160,7 @@
private IOnBackInvokedCallback mBackToLauncherCallback;
private IRemoteAnimationRunner mBackToLauncherRunner;
private IDragAndDrop mDragAndDrop;
- private IHomeTransitionListener mHomeTransitionListener;
+ private final HomeVisibilityState mHomeVisibilityState = new HomeVisibilityState();
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -171,7 +174,8 @@
private final Handler mAsyncHandler;
// TODO(141886704): Find a way to remove this
- private int mLastSystemUiStateFlags;
+ @SystemUiStateFlags
+ private long mLastSystemUiStateFlags;
/**
* This is a singleton pending intent that is used to start recents via Shell (which is a
@@ -200,6 +204,9 @@
}
@Override
+ public void close() { }
+
+ @Override
public void onBackPressed() {
if (mSystemUiProxy != null) {
try {
@@ -269,7 +276,7 @@
setBubblesListener(mBubblesListener);
registerSplitScreenListener(mSplitScreenListener);
registerSplitSelectListener(mSplitSelectListener);
- setHomeTransitionListener(mHomeTransitionListener);
+ mHomeVisibilityState.init(mShellTransitions);
setStartingWindowListener(mStartingWindowListener);
setLauncherUnlockAnimationController(
mLauncherActivityClass, mLauncherUnlockAnimationController);
@@ -320,12 +327,13 @@
}
// TODO(141886704): Find a way to remove this
- public void setLastSystemUiStateFlags(int stateFlags) {
+ public void setLastSystemUiStateFlags(@SystemUiStateFlags long stateFlags) {
mLastSystemUiStateFlags = stateFlags;
}
// TODO(141886704): Find a way to remove this
- public int getLastSystemUiStateFlags() {
+ @SystemUiStateFlags
+ public long getLastSystemUiStateFlags() {
return mLastSystemUiStateFlags;
}
@@ -455,6 +463,18 @@
}
@Override
+ public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier,
+ boolean haptic) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.setOverrideHomeButtonLongPress(duration, slopMultiplier, haptic);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setOverrideHomeButtonLongPress", e);
+ }
+ }
+ }
+
+ @Override
public void notifyAccessibilityButtonClicked(int displayId) {
if (mSystemUiProxy != null) {
try {
@@ -559,6 +579,17 @@
}
}
+ @Override
+ public void toggleQuickSettingsPanel() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.toggleQuickSettingsPanel();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call toggleQuickSettingsPanel", e);
+ }
+ }
+ }
+
//
// Pip
//
@@ -653,11 +684,11 @@
* should be responsible for cleaning up the overlay.
*/
public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay, Rect appBounds) {
+ SurfaceControl overlay, Rect appBounds, Rect sourceRectHint) {
if (mPip != null) {
try {
mPip.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
- appBounds);
+ appBounds, sourceRectHint);
} catch (RemoteException e) {
Log.w(TAG, "Failed call stopSwipePipToHome");
}
@@ -731,12 +762,12 @@
/**
* Tells SysUI to show the bubble with the provided key.
* @param key the key of the bubble to show.
- * @param bubbleBarBounds bounds of the bubble bar in display coordinates
+ * @param top top coordinate of bubble bar on screen
*/
- public void showBubble(String key, Rect bubbleBarBounds) {
+ public void showBubble(String key, int top) {
if (mBubbles != null) {
try {
- mBubbles.showBubble(key, bubbleBarBounds);
+ mBubbles.showBubble(key, top);
} catch (RemoteException e) {
Log.w(TAG, "Failed call showBubble");
}
@@ -744,19 +775,6 @@
}
/**
- * Tells SysUI to remove the bubble with the provided key.
- * @param key the key of the bubble to show.
- */
- public void removeBubble(String key) {
- if (mBubbles == null) return;
- try {
- mBubbles.removeBubble(key);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call removeBubble");
- }
- }
-
- /**
* Tells SysUI to remove all bubbles.
*/
public void removeAllBubbles() {
@@ -784,15 +802,43 @@
/**
* Tells SysUI when the bubble is being dragged.
* Should be called only when the bubble bar is expanded.
- * @param bubbleKey the key of the bubble to collapse/expand
- * @param isBeingDragged whether the bubble is being dragged
+ * @param bubbleKey key of the bubble being dragged
*/
- public void onBubbleDrag(@Nullable String bubbleKey, boolean isBeingDragged) {
+ public void startBubbleDrag(@Nullable String bubbleKey) {
if (mBubbles == null) return;
try {
- mBubbles.onBubbleDrag(bubbleKey, isBeingDragged);
+ mBubbles.startBubbleDrag(bubbleKey);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call onBubbleDrag");
+ Log.w(TAG, "Failed call startBubbleDrag");
+ }
+ }
+
+ /**
+ * Tells SysUI when the bubble stops being dragged.
+ * Should be called only when the bubble bar is expanded.
+ *
+ * @param location location of the bubble bar
+ * @param top new top coordinate for bubble bar on screen
+ */
+ public void stopBubbleDrag(BubbleBarLocation location, int top) {
+ if (mBubbles == null) return;
+ try {
+ mBubbles.stopBubbleDrag(location, top);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call stopBubbleDrag");
+ }
+ }
+
+ /**
+ * Tells SysUI to dismiss the bubble with the provided key.
+ * @param key the key of the bubble to dismiss.
+ */
+ public void dragBubbleToDismiss(String key) {
+ if (mBubbles == null) return;
+ try {
+ mBubbles.dragBubbleToDismiss(key);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call dragBubbleToDismiss");
}
}
@@ -808,6 +854,33 @@
}
}
+ /**
+ * Tells SysUI to update the bubble bar location to the new location.
+ * @param location new location for the bubble bar
+ */
+ public void setBubbleBarLocation(BubbleBarLocation location) {
+ try {
+ mBubbles.setBubbleBarLocation(location);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setBubbleBarLocation");
+ }
+ }
+
+ /**
+ * Tells SysUI the top coordinate of bubble bar on screen
+ *
+ * @param topOnScreen top coordinate for bubble bar on screen
+ */
+ public void updateBubbleBarTopOnScreen(int topOnScreen) {
+ try {
+ if (mBubbles != null) {
+ mBubbles.updateBubbleBarTopOnScreen(topOnScreen);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call updateBubbleBarTopOnScreen");
+ }
+ }
+
//
// Splitscreen
//
@@ -1102,22 +1175,8 @@
mRemoteTransitions.remove(remoteTransition);
}
- public void setHomeTransitionListener(IHomeTransitionListener listener) {
- if (!FeatureFlags.enableHomeTransitionListener()) {
- return;
- }
-
- mHomeTransitionListener = listener;
-
- if (mShellTransitions != null) {
- try {
- mShellTransitions.setHomeTransitionListener(listener);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setHomeTransitionListener", e);
- }
- } else {
- Log.w(TAG, "Unable to call setHomeTransitionListener because ShellTransitions is null");
- }
+ public HomeVisibilityState getHomeVisibilityState() {
+ return mHomeVisibilityState;
}
/**
@@ -1344,8 +1403,7 @@
* Gets the set of running tasks.
*/
public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
- if (mRecentTasks != null
- && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC)) {
+ if (mRecentTasks != null && shouldEnableRunningTasksForDesktopMode()) {
try {
return new ArrayList<>(Arrays.asList(mRecentTasks.getRunningTasks(numTasks)));
} catch (RemoteException e) {
@@ -1355,6 +1413,11 @@
return new ArrayList<>();
}
+ private boolean shouldEnableRunningTasksForDesktopMode() {
+ // TODO(b/335401172): unify DesktopMode checks in Launcher
+ return enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps();
+ }
+
private boolean handleMessageAsync(Message msg) {
switch (msg.what) {
case MSG_SET_SHELF_HEIGHT:
@@ -1383,28 +1446,6 @@
}
}
- /** Call shell to stash desktop apps */
- public void stashDesktopApps(int displayId) {
- if (mDesktopMode != null) {
- try {
- mDesktopMode.stashDesktopApps(displayId);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call stashDesktopApps", e);
- }
- }
- }
-
- /** Call shell to hide desktop apps that may be stashed */
- public void hideStashedDesktopApps(int displayId) {
- if (mDesktopMode != null) {
- try {
- mDesktopMode.hideStashedDesktopApps(displayId);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call hideStashedDesktopApps", e);
- }
- }
- }
-
/**
* If task with the given id is on the desktop, bring it to front
*/
@@ -1453,6 +1494,17 @@
}
}
+ /** Call shell to move a task with given `taskId` to desktop */
+ public void moveToDesktop(int taskId, DesktopModeTransitionSource transitionSource) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.moveToDesktop(taskId, transitionSource);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call moveToDesktop", e);
+ }
+ }
+ }
+
//
// Unfold transition
//
@@ -1558,7 +1610,7 @@
pw.println("\tmSplitSelectListener=" + mSplitSelectListener);
pw.println("\tmOneHanded=" + mOneHanded);
pw.println("\tmShellTransitions=" + mShellTransitions);
- pw.println("\tmHomeTransitionListener=" + mHomeTransitionListener);
+ pw.println("\tmHomeVisibilityState=" + mHomeVisibilityState);
pw.println("\tmStartingWindow=" + mStartingWindow);
pw.println("\tmStartingWindowListener=" + mStartingWindowListener);
pw.println("\tmSysuiUnlockAnimationController=" + mSysuiUnlockAnimationController);
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 4e62d60..28fa81a 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -17,6 +17,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static com.android.launcher3.Flags.enableHandleDelayedGestureCallbacks;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
@@ -25,6 +26,8 @@
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -43,20 +46,23 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.SystemUiFlagUtils;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import java.io.PrintWriter;
import java.util.HashMap;
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
- public static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+ public static final boolean ENABLE_SHELL_TRANSITIONS = true;
public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
&& SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
+ private final Context mCtx;
private RecentsAnimationController mController;
private RecentsAnimationCallbacks mCallbacks;
private RecentsAnimationTargets mTargets;
@@ -64,9 +70,9 @@
private GestureState mLastGestureState;
private RemoteAnimationTarget[] mLastAppearedTaskTargets;
private Runnable mLiveTileCleanUpHandler;
- private Context mCtx;
private boolean mRecentsAnimationStartPending = false;
+ private boolean mShouldIgnoreMotionEvents = false;
private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
@Override
@@ -77,10 +83,11 @@
mLiveTileRestartListener);
return;
}
- BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
- if (activityInterface.isInLiveTileMode()
- && activityInterface.getCreatedActivity() != null) {
- RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
+ BaseContainerInterface containerInterface = mLastGestureState.getContainerInterface();
+ if (containerInterface.isInLiveTileMode()
+ && containerInterface.getCreatedContainer() != null) {
+ RecentsView recentsView = containerInterface.getCreatedContainer()
+ .getOverviewPanel();
if (recentsView != null) {
recentsView.launchSideTaskInLiveTileModeForRestartedApp(task.taskId);
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
@@ -102,8 +109,16 @@
.startRecentsActivity(intent, 0, null, null, null));
}
- public boolean isRecentsAnimationStartPending() {
- return mRecentsAnimationStartPending;
+ boolean shouldIgnoreMotionEvents() {
+ return mShouldIgnoreMotionEvents;
+ }
+
+ void notifyNewGestureStart() {
+ // If mRecentsAnimationStartPending is true at the beginning of a gesture, block all motion
+ // events for this new gesture so that this new gesture does not interfere with the
+ // previously-requested recents animation. Otherwise, clean up mShouldIgnoreMotionEvents.
+ // NOTE: this can lead to misleading logs
+ mShouldIgnoreMotionEvents = mRecentsAnimationStartPending;
}
/**
@@ -135,16 +150,21 @@
cleanUpRecentsAnimation(mCallbacks);
}
- final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
+ final BaseContainerInterface containerInterface = gestureState.getContainerInterface();
mLastGestureState = gestureState;
RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(
- SystemUiProxy.INSTANCE.get(mCtx), activityInterface.allowMinimizeSplitScreen());
+ SystemUiProxy.INSTANCE.get(mCtx), containerInterface.allowMinimizeSplitScreen());
mCallbacks = newCallbacks;
mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
- mRecentsAnimationStartPending = false;
+ if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation(onRecentsAnimationStart): ")
+ .append("Setting mRecentsAnimationStartPending = false"));
+ mRecentsAnimationStartPending = false;
+ }
if (mCallbacks == null) {
// It's possible for the recents animation to have finished and be cleaned up
// by the time we process the start callback, and in that case, just we can skip
@@ -185,13 +205,25 @@
@Override
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
- mRecentsAnimationStartPending = false;
+ if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation")
+ .append("(onRecentsAnimationCanceled): ")
+ .append("Setting mRecentsAnimationStartPending = false"));
+ mRecentsAnimationStartPending = false;
+ }
cleanUpRecentsAnimation(newCallbacks);
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
- mRecentsAnimationStartPending = false;
+ if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation")
+ .append("(onRecentsAnimationFinished): ")
+ .append("Setting mRecentsAnimationStartPending = false"));
+ mRecentsAnimationStartPending = false;
+ }
cleanUpRecentsAnimation(newCallbacks);
}
@@ -208,17 +240,18 @@
@Override
public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
- BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
+ BaseContainerInterface containerInterface =
+ mLastGestureState.getContainerInterface();
for (RemoteAnimationTarget compat : appearedTaskTargets) {
if (compat.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME
- && activityInterface.getCreatedActivity() instanceof RecentsActivity
+ && containerInterface.getCreatedContainer() instanceof RecentsActivity
&& DisplayController.getNavigationMode(mCtx) != NO_BUTTON) {
// The only time we get onTasksAppeared() in button navigation with a
// 3p launcher is if the user goes to overview first, and in this case we
// can immediately finish the transition
RecentsView recentsView =
- activityInterface.getCreatedActivity().getOverviewPanel();
+ containerInterface.getCreatedContainer().getOverviewPanel();
if (recentsView != null) {
recentsView.finishRecentsAnimation(true, null);
}
@@ -232,12 +265,12 @@
if (nonAppTargets == null) {
nonAppTargets = new RemoteAnimationTarget[0];
}
- if ((activityInterface.isInLiveTileMode()
+ if ((containerInterface.isInLiveTileMode()
|| mLastGestureState.getEndTarget() == RECENTS
|| isNonRecentsStartedTasksAppeared(appearedTaskTargets))
- && activityInterface.getCreatedActivity() != null) {
+ && containerInterface.getCreatedContainer() != null) {
RecentsView recentsView =
- activityInterface.getCreatedActivity().getOverviewPanel();
+ containerInterface.getCreatedContainer().getOverviewPanel();
if (recentsView != null) {
ActiveGestureLog.INSTANCE.addLog(
new ActiveGestureLog.CompoundString("Launching side task id=")
@@ -272,13 +305,13 @@
@Override
public boolean onSwitchToScreenshot(Runnable onFinished) {
- if (!activityInterface.isInLiveTileMode()
- || activityInterface.getCreatedActivity() == null) {
+ if (!containerInterface.isInLiveTileMode()
+ || containerInterface.getCreatedContainer() == null) {
// No need to switch since tile is already a screenshot.
onFinished.run();
} else {
final RecentsView recentsView =
- activityInterface.getCreatedActivity().getOverviewPanel();
+ containerInterface.getCreatedContainer().getOverviewPanel();
if (recentsView != null) {
recentsView.switchToScreenshot(onFinished);
} else {
@@ -295,19 +328,35 @@
if (ENABLE_SHELL_TRANSITIONS) {
final ActivityOptions options = ActivityOptions.makeBasic();
// Use regular (non-transient) launch for all apps page to control IME.
- if (!activityInterface.allowAllAppsFromOverview()) {
+ if (!containerInterface.allowAllAppsFromOverview()) {
options.setTransientLaunch();
}
options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
- mRecentsAnimationStartPending = SystemUiProxy.INSTANCE.getNoCreate()
+ mRecentsAnimationStartPending = SystemUiProxy.INSTANCE.get(mCtx)
.startRecentsActivity(intent, options, mCallbacks);
+ if (enableHandleDelayedGestureCallbacks()) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation(shell transition path): ")
+ .append("Setting mRecentsAnimationStartPending = ")
+ .append(mRecentsAnimationStartPending));
+ }
} else {
UI_HELPER_EXECUTOR.execute(
() -> ActivityManagerWrapper.getInstance().startRecentsActivity(
intent,
eventTime,
mCallbacks,
- result -> mRecentsAnimationStartPending = result,
+ result -> {
+ if (enableHandleDelayedGestureCallbacks()) {
+ ActiveGestureLog.INSTANCE.addLog(
+ new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation")
+ .append("(legacy path): Setting ")
+ .append("mRecentsAnimationStartPending = ")
+ .append(result));
+ }
+ mRecentsAnimationStartPending = result;
+ },
MAIN_EXECUTOR.getHandler()));
}
gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED);
@@ -328,20 +377,54 @@
return mCallbacks;
}
- public void endLiveTile() {
- if (mLastGestureState == null) {
- return;
- }
- BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
- if (activityInterface.isInLiveTileMode()
- && activityInterface.getCreatedActivity() != null) {
- RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
- if (recentsView != null) {
- recentsView.switchToScreenshot(null,
- () -> recentsView.finishRecentsAnimation(true /* toRecents */,
- false /* shouldPip */, null));
+ public void onSystemUiFlagsChanged(@QuickStepContract.SystemUiStateFlags long lastSysUIFlags,
+ @QuickStepContract.SystemUiStateFlags long newSysUIFlags) {
+ long isShadeExpandedFlagMask =
+ SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+ boolean wasExpanded = hasAnyFlag(lastSysUIFlags, isShadeExpandedFlagMask);
+ boolean isExpanded = hasAnyFlag(newSysUIFlags, isShadeExpandedFlagMask);
+ if (wasExpanded != isExpanded && isExpanded) {
+ // End live tile when expanding the notification panel for the first time from
+ // overview.
+ if (endLiveTile()) {
+ return;
}
}
+
+ boolean wasLocked = SystemUiFlagUtils.isLocked(lastSysUIFlags);
+ boolean isLocked = SystemUiFlagUtils.isLocked(newSysUIFlags);
+ if (wasLocked != isLocked && isLocked) {
+ // Finish the running recents animation when locking the device.
+ finishRunningRecentsAnimation(
+ mController != null && mController.getFinishTargetIsLauncher());
+ }
+ }
+
+ private boolean hasAnyFlag(long flags, long flagMask) {
+ return (flags & flagMask) != 0;
+ }
+
+ /**
+ * Switches the {@link RecentsView} to screenshot if in live tile mode.
+ *
+ * @return true iff the {@link RecentsView} was in live tile mode and was switched to screenshot
+ */
+ public boolean endLiveTile() {
+ if (mLastGestureState == null) {
+ return false;
+ }
+ BaseContainerInterface containerInterface = mLastGestureState.getContainerInterface();
+ if (!containerInterface.isInLiveTileMode()
+ || containerInterface.getCreatedContainer() == null) {
+ return false;
+ }
+ RecentsView recentsView = containerInterface.getCreatedContainer().getOverviewPanel();
+ if (recentsView == null) {
+ return false;
+ }
+ recentsView.switchToScreenshot(null, () -> recentsView.finishRecentsAnimation(
+ true /* toRecents */, false /* shouldPip */, null));
+ return true;
}
public void setLiveTileCleanUpHandler(Runnable cleanUpHandler) {
@@ -437,7 +520,24 @@
return mCallbacks;
}
- public void dump() {
- // TODO
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "TaskAnimationManager:");
+
+ if (enableHandleDelayedGestureCallbacks()) {
+ pw.println(prefix + "\tmRecentsAnimationStartPending=" + mRecentsAnimationStartPending);
+ pw.println(prefix + "\tmShouldIgnoreUpcomingGestures=" + mShouldIgnoreMotionEvents);
+ }
+ if (mController != null) {
+ mController.dump(prefix + '\t', pw);
+ }
+ if (mCallbacks != null) {
+ mCallbacks.dump(prefix + '\t', pw);
+ }
+ if (mTargets != null) {
+ mTargets.dump(prefix + '\t', pw);
+ }
+ if (mLastGestureState != null) {
+ mLastGestureState.dump(prefix + '\t', pw);
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 312cdc9..b183ae3 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,8 +16,6 @@
package com.android.quickstep;
-import static android.view.Surface.ROTATION_0;
-
import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
@@ -33,8 +31,6 @@
import androidx.annotation.RequiresApi;
import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -43,11 +39,14 @@
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.Snackbar;
import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.views.DesktopTaskView;
+import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.TaskThumbnailViewDeprecated;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -60,52 +59,30 @@
public class TaskOverlayFactory implements ResourceBasedOverride {
public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
- final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
- boolean hasMultipleTasks = taskView.getTaskIds()[1] != -1;
+ final RecentsViewContainer container =
+ RecentsViewContainer.containerFromContext(taskView.getContext());
for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
- if (hasMultipleTasks && !menuOption.showForSplitscreen()) {
+ if (taskView instanceof GroupedTaskView && !menuOption.showForGroupedTask()) {
+ continue;
+ }
+ if (taskView instanceof DesktopTaskView && !menuOption.showForDesktopTask()) {
continue;
}
- List<SystemShortcut> menuShortcuts = menuOption.getShortcuts(activity, taskContainer);
+ List<SystemShortcut> menuShortcuts = menuOption.getShortcuts(container, taskContainer);
if (menuShortcuts == null) {
continue;
}
shortcuts.addAll(menuShortcuts);
}
- RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState();
- boolean canLauncherRotate = orientedState.isRecentsActivityRotationAllowed();
- boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
- boolean isTablet = activity.getDeviceProfile().isTablet;
-
- boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
- // Add overview actions to the menu when in in-place rotate landscape mode, or in
- // grid-only overview.
- if ((!canLauncherRotate && isInLandscape) || isGridOnlyOverview) {
- // Add screenshot action to task menu.
- List<SystemShortcut> screenshotShortcuts = TaskShortcutFactory.SCREENSHOT
- .getShortcuts(activity, taskContainer);
- if (screenshotShortcuts != null) {
- shortcuts.addAll(screenshotShortcuts);
- }
-
- // Add modal action only if display orientation is the same as the device orientation,
- // or in grid-only overview.
- if (orientedState.getDisplayRotation() == ROTATION_0 || isGridOnlyOverview) {
- List<SystemShortcut> modalShortcuts = TaskShortcutFactory.MODAL
- .getShortcuts(activity, taskContainer);
- if (modalShortcuts != null) {
- shortcuts.addAll(modalShortcuts);
- }
- }
- }
return shortcuts;
}
- public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
- return new TaskOverlay(thumbnailView);
+ /** Creates a {@link TaskOverlay} associated with the provide {@link TaskContainer}. */
+ public TaskOverlay<?> createOverlay(TaskContainer taskContainer) {
+ return new TaskOverlay<>(taskContainer);
}
/**
@@ -135,8 +112,11 @@
TaskShortcutFactory.PIN,
TaskShortcutFactory.INSTALL,
TaskShortcutFactory.FREE_FORM,
+ DesktopSystemShortcut.Companion.createFactory(),
TaskShortcutFactory.WELLBEING,
- TaskShortcutFactory.SAVE_APP_PAIR
+ TaskShortcutFactory.SAVE_APP_PAIR,
+ TaskShortcutFactory.SCREENSHOT,
+ TaskShortcutFactory.MODAL
};
/**
@@ -145,28 +125,29 @@
public static class TaskOverlay<T extends OverviewActionsView> {
protected final Context mApplicationContext;
- protected final TaskThumbnailView mThumbnailView;
+ protected final TaskContainer mTaskContainer;
private T mActionsView;
protected ImageActionsApi mImageApi;
- protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
- mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
- mThumbnailView = taskThumbnailView;
+ protected TaskOverlay(TaskContainer taskContainer) {
+ mApplicationContext = taskContainer.getTaskView().getContext().getApplicationContext();
+ mTaskContainer = taskContainer;
mImageApi = new ImageActionsApi(
- mApplicationContext, mThumbnailView::getThumbnail);
+ mApplicationContext, mTaskContainer.getThumbnailViewDeprecated()::getThumbnail);
}
protected T getActionsView() {
if (mActionsView == null) {
- mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
+ mActionsView = BaseActivity.fromContext(
+ mTaskContainer.getThumbnailViewDeprecated().getContext()).findViewById(
R.id.overview_actions_view);
}
return mActionsView;
}
- public TaskThumbnailView getThumbnailView() {
- return mThumbnailView;
+ public TaskThumbnailViewDeprecated getThumbnailView() {
+ return mTaskContainer.getThumbnailViewDeprecated();
}
/**
@@ -178,7 +159,8 @@
if (thumbnail != null) {
getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
- boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot();
+ boolean isAllowedByPolicy =
+ mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot();
getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task));
}
}
@@ -189,7 +171,8 @@
* @param callback callback to run, after switching to screenshot
*/
public void endLiveTileMode(@NonNull Runnable callback) {
- RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
+ RecentsView recentsView =
+ mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView();
// Task has already been dismissed
if (recentsView == null) return;
recentsView.switchToScreenshot(
@@ -202,19 +185,28 @@
*/
@SuppressLint("NewApi")
protected void saveScreenshot(Task task) {
- if (mThumbnailView.isRealSnapshot()) {
- mImageApi.saveScreenshot(mThumbnailView.getThumbnail(),
+ if (mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot()) {
+ mImageApi.saveScreenshot(mTaskContainer.getThumbnailViewDeprecated().getThumbnail(),
getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
} else {
showBlockedByPolicyMessage();
}
}
- private void enterSplitSelect() {
- RecentsView overviewPanel = mThumbnailView.getTaskView().getRecentsView();
+ protected void enterSplitSelect() {
+ RecentsView overviewPanel =
+ mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView();
// Task has already been dismissed
if (overviewPanel == null) return;
- overviewPanel.initiateSplitSelect(mThumbnailView.getTaskView());
+ overviewPanel.initiateSplitSelect(
+ mTaskContainer.getThumbnailViewDeprecated().getTaskView());
+ }
+
+ protected void saveAppPair() {
+ GroupedTaskView taskView =
+ (GroupedTaskView) mTaskContainer.getThumbnailViewDeprecated().getTaskView();
+ taskView.getRecentsView().getSplitSelectController().getAppPairsController()
+ .saveAppPair(taskView);
}
/**
@@ -246,9 +238,9 @@
/**
* Gets the system shortcut for the screenshot that will be added to the task menu.
*/
- public SystemShortcut getScreenshotShortcut(BaseDraggingActivity activity,
+ public SystemShortcut getScreenshotShortcut(RecentsViewContainer container,
ItemInfo iteminfo, View originalView) {
- return new ScreenshotSystemShortcut(activity, iteminfo, originalView);
+ return new ScreenshotSystemShortcut(container, iteminfo, originalView);
}
/**
@@ -258,10 +250,11 @@
*/
public Rect getTaskSnapshotBounds() {
int[] location = new int[2];
- mThumbnailView.getLocationOnScreen(location);
+ mTaskContainer.getThumbnailViewDeprecated().getLocationOnScreen(location);
- return new Rect(location[0], location[1], mThumbnailView.getWidth() + location[0],
- mThumbnailView.getHeight() + location[1]);
+ return new Rect(location[0], location[1],
+ mTaskContainer.getThumbnailViewDeprecated().getWidth() + location[0],
+ mTaskContainer.getThumbnailViewDeprecated().getHeight() + location[1]);
}
/**
@@ -271,7 +264,7 @@
*/
@RequiresApi(api = Build.VERSION_CODES.Q)
public Insets getTaskSnapshotInsets() {
- return mThumbnailView.getScaledInsets();
+ return mTaskContainer.getThumbnailViewDeprecated().getScaledInsets();
}
/**
@@ -282,33 +275,38 @@
protected void showBlockedByPolicyMessage() {
ActivityContext activityContext = ActivityContext.lookupContext(
- mThumbnailView.getContext());
+ mTaskContainer.getThumbnailViewDeprecated().getContext());
String message = activityContext.getStringCache() != null
? activityContext.getStringCache().disabledByAdminMessage
- : mThumbnailView.getContext().getString(R.string.blocked_by_policy);
+ : mTaskContainer.getThumbnailViewDeprecated().getContext().getString(
+ R.string.blocked_by_policy);
- Snackbar.show(BaseActivity.fromContext(mThumbnailView.getContext()), message, null);
+ Snackbar.show(BaseActivity.fromContext(
+ mTaskContainer.getThumbnailViewDeprecated().getContext()), message, null);
}
/** Called when the snapshot has updated its full screen drawing parameters. */
- public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
- }
+ public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {}
+
+ /** Sets visibility for the overlay associated elements. */
+ public void setVisibility(int visibility) {}
private class ScreenshotSystemShortcut extends SystemShortcut {
- private final BaseDraggingActivity mActivity;
+ private final RecentsViewContainer mContainer;
- ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo,
+ ScreenshotSystemShortcut(RecentsViewContainer container, ItemInfo itemInfo,
View originalView) {
- super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo,
+ super(R.drawable.ic_screenshot, R.string.action_screenshot, container, itemInfo,
originalView);
- mActivity = activity;
+ mContainer = container;
}
@Override
public void onClick(View view) {
- saveScreenshot(mThumbnailView.getTaskView().getTask());
- dismissTaskMenuView(mActivity);
+ saveScreenshot(
+ mTaskContainer.getThumbnailViewDeprecated().getTaskView().getFirstTask());
+ dismissTaskMenuView();
}
}
@@ -329,6 +327,10 @@
public void onSplit() {
endLiveTileMode(TaskOverlay.this::enterSplitSelect);
}
+
+ public void onSaveAppPair() {
+ endLiveTileMode(TaskOverlay.this::saveAppPair);
+ }
}
}
@@ -342,5 +344,8 @@
/** User wants to start split screen with current app. */
void onSplit();
+
+ /** User wants to save an app pair with current group of apps. */
+ void onSaveAppPair();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 9c84df8..d18c86e 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -18,11 +18,12 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.window.flags.Flags.enableDesktopWindowingMode;
-import android.app.Activity;
import android.app.ActivityOptions;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -39,22 +40,24 @@
import androidx.annotation.Nullable;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.WellbeingModel;
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.SplitPositionOption;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.TaskThumbnailViewDeprecated;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
@@ -72,12 +75,22 @@
*/
public interface TaskShortcutFactory {
@Nullable
- default List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer) {
+ default List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
return null;
}
- default boolean showForSplitscreen() {
+ /**
+ * Returns {@code true} if it should be shown for grouped task; {@code false} otherwise.
+ */
+ default boolean showForGroupedTask() {
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if it should be shown for desktop task; {@code false} otherwise.
+ */
+ default boolean showForDesktopTask() {
return false;
}
@@ -92,20 +105,24 @@
TaskShortcutFactory APP_INFO = new TaskShortcutFactory() {
@Override
- public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer) {
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
TaskView taskView = taskContainer.getTaskView();
+ int actionId = taskContainer.getStagePosition() == STAGE_POSITION_BOTTOM_OR_RIGHT
+ ? R.id.action_app_info_bottom_right
+ : R.id.action_app_info_top_left;
+
AppInfo.SplitAccessibilityInfo accessibilityInfo =
new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(),
TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
- taskContainer.getA11yNodeId()
+ actionId
);
- return Collections.singletonList(new AppInfo(activity, taskContainer.getItemInfo(),
+ return Collections.singletonList(new AppInfo(container, taskContainer.getItemInfo(),
taskView, accessibilityInfo));
}
@Override
- public boolean showForSplitscreen() {
+ public boolean showForGroupedTask() {
return true;
}
};
@@ -114,9 +131,10 @@
private final TaskView mTaskView;
private final SplitPositionOption mSplitPositionOption;
- public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
+ public SplitSelectSystemShortcut(RecentsViewContainer container, TaskView taskView,
SplitPositionOption option) {
- super(option.iconResId, option.textResId, target, taskView.getItemInfo(), taskView);
+ super(option.iconResId, option.textResId, container, taskView.getFirstItemInfo(),
+ taskView);
mTaskView = taskView;
mSplitPositionOption = option;
}
@@ -131,47 +149,49 @@
* A menu item, "Save app pair", that allows the user to preserve the current app combination as
* one persistent icon on the Home screen, allowing for quick split screen launching.
*/
- class SaveAppPairSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
+ class SaveAppPairSystemShortcut extends SystemShortcut<RecentsViewContainer> {
private final GroupedTaskView mTaskView;
- public SaveAppPairSystemShortcut(BaseDraggingActivity activity, GroupedTaskView taskView) {
- super(R.drawable.ic_save_app_pair, R.string.save_app_pair, activity,
- taskView.getItemInfo(), taskView);
+
+ public SaveAppPairSystemShortcut(RecentsViewContainer container, GroupedTaskView taskView,
+ int iconResId) {
+ super(iconResId, R.string.save_app_pair, container, taskView.getFirstItemInfo(),
+ taskView);
mTaskView = taskView;
}
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
((RecentsView) mTarget.getOverviewPanel())
.getSplitSelectController().getAppPairsController().saveAppPair(mTaskView);
}
}
- class FreeformSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
+ class FreeformSystemShortcut extends SystemShortcut<RecentsViewContainer> {
private static final String TAG = "FreeformSystemShortcut";
private Handler mHandler;
private final RecentsView mRecentsView;
- private final TaskThumbnailView mThumbnailView;
+ private final TaskThumbnailViewDeprecated mThumbnailView;
private final TaskView mTaskView;
private final LauncherEvent mLauncherEvent;
- public FreeformSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer, LauncherEvent launcherEvent) {
- super(iconRes, textRes, activity, taskContainer.getItemInfo(),
+ public FreeformSystemShortcut(int iconRes, int textRes, RecentsViewContainer container,
+ TaskContainer taskContainer, LauncherEvent launcherEvent) {
+ super(iconRes, textRes, container, taskContainer.getItemInfo(),
taskContainer.getTaskView());
mLauncherEvent = launcherEvent;
mHandler = new Handler(Looper.getMainLooper());
mTaskView = taskContainer.getTaskView();
- mRecentsView = activity.getOverviewPanel();
- mThumbnailView = taskContainer.getThumbnailView();
+ mRecentsView = container.getOverviewPanel();
+ mThumbnailView = taskContainer.getThumbnailViewDeprecated();
}
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
RecentsView rv = mTarget.getOverviewPanel();
rv.switchToScreenshot(() -> {
rv.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> {
@@ -182,7 +202,7 @@
}
private void startActivity() {
- final Task.TaskKey taskKey = mTaskView.getTask().key;
+ final Task.TaskKey taskKey = mTaskView.getFirstTask().key;
final int taskId = taskKey.id;
final ActivityOptions options = makeLaunchOptions(mTarget);
if (options != null) {
@@ -226,7 +246,7 @@
overridePendingAppTransitionMultiThumbFuture(
future, animStartedListener, mHandler, true /* scaleUp */,
taskKey.displayId);
- mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
.log(mLauncherEvent);
}
}
@@ -249,11 +269,11 @@
}
}
- private ActivityOptions makeLaunchOptions(Activity activity) {
+ private ActivityOptions makeLaunchOptions(RecentsViewContainer container) {
ActivityOptions activityOptions = ActivityOptions.makeBasic();
activityOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
// Arbitrary bounds only because freeform is in dev mode right now
- final View decorView = activity.getWindow().getDecorView();
+ final View decorView = container.getWindow().getDecorView();
final WindowInsets insets = decorView.getRootWindowInsets();
final Rect r = new Rect(0, 0, decorView.getWidth() / 2, decorView.getHeight() / 2);
r.offsetTo(insets.getSystemWindowInsetLeft() + 50,
@@ -274,9 +294,9 @@
*/
TaskShortcutFactory SPLIT_SELECT = new TaskShortcutFactory() {
@Override
- public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer) {
- DeviceProfile deviceProfile = activity.getDeviceProfile();
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ DeviceProfile deviceProfile = container.getDeviceProfile();
final Task task = taskContainer.getTask();
final int intentFlags = task.key.baseIntent.getFlags();
final TaskView taskView = taskContainer.getTaskView();
@@ -288,7 +308,7 @@
!deviceProfile.isTaskbarPresent && recentsView.getTaskViewCount() < 2;
boolean isTaskSplitNotSupported = !task.isDockable ||
(intentFlags & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
- boolean hideForExistingMultiWindow = activity.getDeviceProfile().isMultiWindowMode;
+ boolean hideForExistingMultiWindow = container.getDeviceProfile().isMultiWindowMode;
boolean isFocusedTask = deviceProfile.isTablet && taskView.isFocusedTask();
boolean isTaskInExpectedScrollPosition =
recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
@@ -301,7 +321,7 @@
return orientationHandler.getSplitPositionOptions(deviceProfile)
.stream()
.map((Function<SplitPositionOption, SystemShortcut>) option ->
- new SplitSelectSystemShortcut(activity, taskView, option))
+ new SplitSelectSystemShortcut(container, taskView, option))
.collect(Collectors.toList());
}
};
@@ -309,55 +329,74 @@
TaskShortcutFactory SAVE_APP_PAIR = new TaskShortcutFactory() {
@Nullable
@Override
- public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer) {
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ DeviceProfile deviceProfile = container.getDeviceProfile();
final TaskView taskView = taskContainer.getTaskView();
+ final RecentsView recentsView = taskView.getRecentsView();
+ boolean isLargeTileFocusedTask = deviceProfile.isTablet && taskView.isFocusedTask();
+ boolean isInExpectedScrollPosition =
+ recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
+ boolean shouldShowActionsButtonInstead =
+ isLargeTileFocusedTask && isInExpectedScrollPosition;
- if (!FeatureFlags.enableAppPairs() || !taskView.containsMultipleTasks()) {
+ // No "save app pair" menu item if:
+ // - we are in 3p launcher
+ // - the Overview Actions Button should be visible
+ // - the task view is not a valid save-able split pair
+ if (!recentsView.supportsAppPairs()
+ || shouldShowActionsButtonInstead
+ || !recentsView.getSplitSelectController().getAppPairsController()
+ .canSaveAppPair(taskView)) {
return null;
}
+ int iconResId = deviceProfile.isLeftRightSplit
+ ? R.drawable.ic_save_app_pair_left_right
+ : R.drawable.ic_save_app_pair_up_down;
+
return Collections.singletonList(
- new SaveAppPairSystemShortcut(activity, (GroupedTaskView) taskView));
+ new SaveAppPairSystemShortcut(container,
+ (GroupedTaskView) taskView, iconResId));
}
@Override
- public boolean showForSplitscreen() {
+ public boolean showForGroupedTask() {
return true;
}
};
TaskShortcutFactory FREE_FORM = new TaskShortcutFactory() {
@Override
- public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer) {
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
final Task task = taskContainer.getTask();
if (!task.isDockable) {
return null;
}
- if (!isAvailable(activity, task.key.displayId)) {
+ if (!isAvailable(container)) {
return null;
}
return Collections.singletonList(new FreeformSystemShortcut(
R.drawable.ic_caption_desktop_button_foreground,
- R.string.recent_task_option_freeform, activity, taskContainer,
+ R.string.recent_task_option_freeform, container, taskContainer,
LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP));
}
- private boolean isAvailable(BaseDraggingActivity activity, int displayId) {
+ private boolean isAvailable(RecentsViewContainer container) {
return Settings.Global.getInt(
- activity.getContentResolver(),
+ container.asContext().getContentResolver(),
Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0
- && !isDesktopModeSupported();
+ && !enableDesktopWindowingMode();
}
};
TaskShortcutFactory PIN = new TaskShortcutFactory() {
@Override
- public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer) {
- if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ if (!SystemUiProxy.INSTANCE.get(container.asContext()).isActive()) {
return null;
}
if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
@@ -367,18 +406,18 @@
// We shouldn't be able to pin while an app is locked.
return null;
}
- return Collections.singletonList(new PinSystemShortcut(activity, taskContainer));
+ return Collections.singletonList(new PinSystemShortcut(container, taskContainer));
}
};
- class PinSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
+ class PinSystemShortcut extends SystemShortcut<RecentsViewContainer> {
private static final String TAG = "PinSystemShortcut";
private final TaskView mTaskView;
- public PinSystemShortcut(BaseDraggingActivity target,
- TaskIdAttributeContainer taskContainer) {
+ 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();
@@ -387,23 +426,23 @@
@Override
public void onClick(View view) {
if (mTaskView.launchTaskAnimated() != null) {
- SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(
- mTaskView.getTask().key.id);
+ SystemUiProxy.INSTANCE.get(mTarget.asContext()).startScreenPinning(
+ mTaskView.getFirstTask().key.id);
}
- dismissTaskMenuView(mTarget);
- mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
+ dismissTaskMenuView();
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
.log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
}
}
TaskShortcutFactory INSTALL = new TaskShortcutFactory() {
@Override
- public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer) {
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
Task t = taskContainer.getTask();
- return InstantAppResolver.newInstance(activity).isInstantApp(
+ return InstantAppResolver.newInstance(container.asContext()).isInstantApp(
t.getTopComponent().getPackageName(), t.getKey().userId)
- ? Collections.singletonList(new SystemShortcut.Install(activity,
+ ? Collections.singletonList(new SystemShortcut.Install(container,
taskContainer.getItemInfo(), taskContainer.getTaskView()))
: null;
}
@@ -411,10 +450,10 @@
TaskShortcutFactory WELLBEING = new TaskShortcutFactory() {
@Override
- public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer) {
- SystemShortcut<BaseDraggingActivity> wellbeingShortcut =
- WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity,
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ SystemShortcut<ActivityContext> wellbeingShortcut =
+ WellbeingModel.SHORTCUT_FACTORY.getShortcut(container,
taskContainer.getItemInfo(), taskContainer.getTaskView());
return createSingletonShortcutList(wellbeingShortcut);
}
@@ -422,24 +461,44 @@
TaskShortcutFactory SCREENSHOT = new TaskShortcutFactory() {
@Override
- public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer) {
- SystemShortcut screenshotShortcut =
- taskContainer.getThumbnailView().getTaskOverlay()
- .getScreenshotShortcut(activity, taskContainer.getItemInfo(),
- taskContainer.getTaskView());
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ boolean isTablet = container.getDeviceProfile().isTablet;
+ boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
+ // Extra conditions if it's not grid-only overview
+ if (!isGridOnlyOverview) {
+ RecentsOrientedState orientedState = taskContainer.getTaskView().getOrientedState();
+ boolean isFakeLandscape = !orientedState.isRecentsActivityRotationAllowed()
+ && orientedState.getTouchRotation() != ROTATION_0;
+ if (!isFakeLandscape) {
+ return null;
+ }
+ }
+
+ SystemShortcut screenshotShortcut = taskContainer.getOverlay().getScreenshotShortcut(
+ container, taskContainer.getItemInfo(), taskContainer.getTaskView());
return createSingletonShortcutList(screenshotShortcut);
}
+
+ @Override
+ public boolean showForDesktopTask() {
+ return true;
+ }
};
TaskShortcutFactory MODAL = new TaskShortcutFactory() {
@Override
- public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
- TaskIdAttributeContainer taskContainer) {
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ boolean isTablet = container.getDeviceProfile().isTablet;
+ boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
+ if (!isGridOnlyOverview) {
+ return null;
+ }
+
SystemShortcut modalStateSystemShortcut =
- taskContainer.getThumbnailView().getTaskOverlay()
- .getModalStateSystemShortcut(
- taskContainer.getItemInfo(), taskContainer.getTaskView());
+ taskContainer.getOverlay().getModalStateSystemShortcut(
+ taskContainer.getItemInfo(), taskContainer.getTaskView());
return createSingletonShortcutList(modalStateSystemShortcut);
}
};
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index b7cbb47..38e927f 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -21,11 +21,13 @@
import android.content.Context;
import android.content.res.Resources;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource;
import com.android.quickstep.util.TaskKeyByLastActiveTimeCache;
import com.android.quickstep.util.TaskKeyCache;
import com.android.quickstep.util.TaskKeyLruCache;
@@ -38,7 +40,7 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-public class TaskThumbnailCache {
+public class TaskThumbnailCache implements TaskThumbnailDataSource {
private final Executor mBgExecutor;
private final TaskKeyCache<ThumbnailData> mCache;
@@ -148,12 +150,13 @@
* @param callback The callback to receive the task after its data has been populated.
* @return A cancelable handle to the request
*/
- public CancellableTask updateThumbnailInBackground(
- Task task, Consumer<ThumbnailData> callback) {
+ @Override
+ public CancellableTask<ThumbnailData> updateThumbnailInBackground(
+ Task task, @NonNull Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
boolean lowResolution = !mHighResLoadingState.isEnabled();
- if (task.thumbnail != null && task.thumbnail.thumbnail != null
+ if (task.thumbnail != null && task.thumbnail.getThumbnail() != null
&& (!task.thumbnail.reducedResolution || lowResolution)) {
// Nothing to load, the thumbnail is already high-resolution or matches what the
// request, so just callback
@@ -184,12 +187,12 @@
return newSize > oldSize;
}
- private CancellableTask updateThumbnailInBackground(TaskKey key, boolean lowResolution,
- Consumer<ThumbnailData> callback) {
+ private CancellableTask<ThumbnailData> updateThumbnailInBackground(TaskKey key,
+ boolean lowResolution, Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
- if (cachedThumbnail != null && cachedThumbnail.thumbnail != null
+ if (cachedThumbnail != null && cachedThumbnail.getThumbnail() != null
&& (!cachedThumbnail.reducedResolution || lowResolution)) {
// Already cached, lets use that thumbnail
callback.accept(cachedThumbnail);
@@ -200,7 +203,7 @@
() -> {
ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance()
.getTaskThumbnail(key.id, lowResolution);
- return thumbnailData.thumbnail != null ? thumbnailData
+ return thumbnailData.getThumbnail() != null ? thumbnailData
: ActivityManagerWrapper.getInstance().takeTaskThumbnail(key.id);
},
MAIN_EXECUTOR,
@@ -210,7 +213,7 @@
if (enableGridOnlyOverview() && result.reducedResolution
&& getHighResLoadingState().isEnabled()) {
ThumbnailData newCachedThumbnail = mCache.getAndInvalidateIfModified(key);
- if (newCachedThumbnail != null && newCachedThumbnail.thumbnail != null
+ if (newCachedThumbnail != null && newCachedThumbnail.getThumbnail() != null
&& !newCachedThumbnail.reducedResolution) {
return;
}
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 80a449b..63e536a 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -70,7 +70,7 @@
return "";
}
UserHandle user = UserHandle.of(userId);
- ApplicationInfo applicationInfo = new PackageManagerHelper(context)
+ ApplicationInfo applicationInfo = PackageManagerHelper.INSTANCE.get(context)
.getApplicationInfo(packageName, user, 0);
if (applicationInfo == null) {
Log.e(TAG, "Failed to get title for userId=" + userId + ", packageName=" + packageName);
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index e30ea7a..ecd84f8 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -37,7 +37,7 @@
import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+import static com.android.quickstep.util.AnimUtils.clampToDuration;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -80,7 +80,7 @@
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.TaskThumbnailViewDeprecated;
import com.android.quickstep.views.TaskView;
import com.android.systemui.animation.RemoteAnimationTargetCompat;
import com.android.systemui.shared.recents.model.Task;
@@ -121,7 +121,7 @@
for (int i = 0; i < recentsView.getTaskViewCount(); i++) {
TaskView taskView = recentsView.getTaskViewAt(i);
if (recentsView.isTaskViewVisible(taskView)) {
- Task.TaskKey key = taskView.getTask().key;
+ Task.TaskKey key = taskView.getFirstTask().key;
if (componentName.equals(key.getComponent()) && userId == key.userId) {
return taskView;
}
@@ -165,8 +165,8 @@
@NonNull RemoteAnimationTarget[] nonAppTargets,
@Nullable DepthController depthController,
PendingAnimation out) {
- boolean isQuickSwitch = v.isEndQuickswitchCuj();
- v.setEndQuickswitchCuj(false);
+ boolean isQuickSwitch = v.isEndQuickSwitchCuj();
+ v.setEndQuickSwitchCuj(false);
final RemoteAnimationTargets targets =
new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
@@ -182,7 +182,7 @@
// Re-use existing handles
remoteTargetHandles = recentsViewHandles;
} else {
- boolean forDesktop = isDesktopModeSupported() && v instanceof DesktopTaskView;
+ boolean forDesktop = v instanceof DesktopTaskView;
RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
recentsView.getSizeStrategy(), targets, forDesktop);
if (forDesktop) {
@@ -267,10 +267,16 @@
if (navBarTarget != null) {
final Rect cropRect = new Rect();
out.addOnFrameListener(new MultiValueUpdateListener() {
- FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0,
- ANIMATION_NAV_FADE_OUT_DURATION, NAV_FADE_OUT_INTERPOLATOR);
- FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN,
- ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR);
+ FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration(
+ NAV_FADE_OUT_INTERPOLATOR,
+ 0,
+ ANIMATION_NAV_FADE_OUT_DURATION,
+ out.getDuration()));
+ FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration(
+ NAV_FADE_IN_INTERPOLATOR,
+ ANIMATION_DELAY_NAV_FADE_IN,
+ ANIMATION_NAV_FADE_IN_DURATION,
+ out.getDuration()));
@Override
public void onUpdate(float percent, boolean initOnly) {
@@ -328,7 +334,7 @@
// During animation we apply transformation on the thumbnailView (and not the rootView)
// to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
// Mt K(0)` K(t) Mt`
- TaskThumbnailView[] thumbnails = v.getThumbnails();
+ TaskThumbnailViewDeprecated[] thumbnails = v.getThumbnailViews();
// In case simulator copies and thumbnail size do no match, ensure we get the lesser.
// This ensures we do not create arrays with empty elements or attempt to references
@@ -338,7 +344,7 @@
Matrix[] mt = new Matrix[matrixSize];
Matrix[] mti = new Matrix[matrixSize];
for (int i = 0; i < matrixSize; i++) {
- TaskThumbnailView ttv = thumbnails[i];
+ TaskThumbnailViewDeprecated ttv = thumbnails[i];
RectF localBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight());
float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()};
getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
@@ -385,7 +391,7 @@
out.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- for (TaskThumbnailView ttv : thumbnails) {
+ for (TaskThumbnailViewDeprecated ttv : thumbnails) {
ttv.setAnimationMatrix(null);
}
}
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index a2a6dde..6ed05c8 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -28,12 +28,14 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitStageInfo;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
@@ -46,6 +48,7 @@
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -57,7 +60,12 @@
* This class tracked the top-most task and some 'approximate' task history to allow faster
* system state estimation during touch interaction
*/
-public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskStackChangeListener {
+public class TopTaskTracker extends ISplitScreenListener.Stub
+ implements TaskStackChangeListener, SafeCloseable {
+
+ private static final String TAG = "TopTaskTracker";
+
+ private static final boolean DEBUG = true;
public static MainThreadInitializedObject<TopTaskTracker> INSTANCE =
new MainThreadInitializedObject<>(TopTaskTracker::new);
@@ -67,12 +75,13 @@
// Ordered list with first item being the most recent task.
private final LinkedList<RunningTaskInfo> mOrderedTaskList = new LinkedList<>();
-
+ private final Context mContext;
private final SplitStageInfo mMainStagePosition = new SplitStageInfo();
private final SplitStageInfo mSideStagePosition = new SplitStageInfo();
private int mPinnedTaskId = INVALID_TASK_ID;
private TopTaskTracker(Context context) {
+ mContext = context;
mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
@@ -81,12 +90,27 @@
}
@Override
+ public void close() {
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(this);
+ SystemUiProxy.INSTANCE.get(mContext).unregisterSplitScreenListener(this);
+ }
+
+ @Override
public void onTaskRemoved(int taskId) {
mOrderedTaskList.removeIf(rto -> rto.taskId == taskId);
+ if (DEBUG) {
+ Log.i(TAG, "onTaskRemoved: taskId=" + taskId);
+ }
}
@Override
public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
+ if (!mOrderedTaskList.isEmpty()
+ && mOrderedTaskList.getFirst().taskId != taskInfo.taskId
+ && DEBUG) {
+ Log.i(TAG, "onTaskMovedToFront: (moved taskInfo to front) taskId=" + taskInfo.taskId
+ + ", baseIntent=" + taskInfo.baseIntent);
+ }
mOrderedTaskList.removeIf(rto -> rto.taskId == taskInfo.taskId);
mOrderedTaskList.addFirst(taskInfo);
@@ -97,6 +121,11 @@
final RunningTaskInfo topTaskOnHomeDisplay = mOrderedTaskList.stream()
.filter(rto -> rto.displayId == DEFAULT_DISPLAY).findFirst().orElse(null);
if (topTaskOnHomeDisplay != null) {
+ if (DEBUG) {
+ Log.i(TAG, "onTaskMovedToFront: (removing top task on home display) taskId="
+ + topTaskOnHomeDisplay.taskId
+ + ", baseIntent=" + topTaskOnHomeDisplay.baseIntent);
+ }
mOrderedTaskList.removeIf(rto -> rto.taskId == topTaskOnHomeDisplay.taskId);
mOrderedTaskList.addFirst(topTaskOnHomeDisplay);
}
@@ -110,6 +139,10 @@
if (info.taskId != taskInfo.taskId
&& info.taskId != mMainStagePosition.taskId
&& info.taskId != mSideStagePosition.taskId) {
+ if (DEBUG) {
+ Log.i(TAG, "onTaskMovedToFront: (removing task list overflow) taskId="
+ + taskInfo.taskId + ", baseIntent=" + taskInfo.baseIntent);
+ }
itr.remove();
return;
}
@@ -119,6 +152,9 @@
@Override
public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
+ if (DEBUG) {
+ Log.i(TAG, "onStagePositionChanged: stage=" + stage + ", position=" + position);
+ }
if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
mMainStagePosition.stagePosition = position;
} else {
@@ -128,6 +164,10 @@
@Override
public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
+ if (DEBUG) {
+ Log.i(TAG, "onTaskStageChanged: taskId=" + taskId
+ + ", stage=" + stage + ", visible=" + visible);
+ }
// If a task is not visible anymore or has been moved to undefined, stop tracking it.
if (!visible || stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
if (mMainStagePosition.taskId == taskId) {
@@ -147,17 +187,24 @@
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+ if (DEBUG) {
+ Log.i(TAG, "onActivityPinned: packageName=" + packageName
+ + ", userId=" + userId + ", stackId=" + stackId);
+ }
mPinnedTaskId = taskId;
}
@Override
public void onActivityUnpinned() {
+ if (DEBUG) {
+ Log.i(TAG, "onActivityUnpinned");
+ }
mPinnedTaskId = INVALID_TASK_ID;
}
/**
* @return index 0 will be task in left/top position, index 1 in right/bottom position.
- * Will return empty array if device is not in staged split
+ * Will return empty array if device is not in staged split
*/
public int[] getRunningSplitTaskIds() {
if (mMainStagePosition.taskId == INVALID_TASK_ID
@@ -203,6 +250,21 @@
return new CachedTaskInfo(tasks);
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "TopTaskTracker:");
+
+ writer.println(prefix + "\tmOrderedTaskList=[");
+ for (RunningTaskInfo taskInfo : mOrderedTaskList) {
+ writer.println(prefix + "\t\t(taskId=" + taskInfo.taskId
+ + "; baseIntent=" + taskInfo.baseIntent
+ + "; isRunning=" + taskInfo.isRunning + ")");
+ }
+ writer.println(prefix + "\t]");
+ writer.println(prefix + "\tmMainStagePosition=" + mMainStagePosition);
+ writer.println(prefix + "\tmSideStagePosition=" + mSideStagePosition);
+ writer.println(prefix + "\tmPinnedTaskId=" + mPinnedTaskId);
+ }
+
/**
* Class to provide information about a task which can be safely cached and do not change
* during the lifecycle of the task.
@@ -233,7 +295,7 @@
* If the given task holds an activity that is excluded from recents, and there
* is another running task that is not excluded from recents, returns that underlying task.
*/
- public @Nullable CachedTaskInfo otherVisibleTaskThisIsExcludedOver() {
+ public @Nullable CachedTaskInfo getVisibleNonExcludedTask() {
if (mTopTask == null
|| (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
// Not an excluded task.
@@ -241,7 +303,9 @@
}
List<RunningTaskInfo> visibleNonExcludedTasks = mAllCachedTasks.stream()
.filter(t -> t.isVisible
- && (t.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)
+ && (t.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0
+ && t.getActivityType() != ACTIVITY_TYPE_HOME
+ && t.getActivityType() != ACTIVITY_TYPE_RECENTS)
.toList();
return visibleNonExcludedTasks.isEmpty() ? null
: new CachedTaskInfo(visibleNonExcludedTasks);
@@ -275,7 +339,7 @@
*/
public Task[] getPlaceholderTasks() {
return mTopTask == null ? new Task[0]
- : new Task[] {Task.from(new TaskKey(mTopTask), mTopTask, false)};
+ : new Task[]{Task.from(new TaskKey(mTopTask), mTopTask, false)};
}
/**
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index b43c520..bfdc3df 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,6 +24,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.enableHandleDelayedGestureCallbacks;
import static com.android.launcher3.Flags.useActivityOverlay;
import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
import static com.android.launcher3.LauncherPrefs.backedUpItem;
@@ -41,12 +42,11 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
-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.wm.shell.Flags.enableBubblesLongPressNavHandle;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
@@ -66,11 +66,13 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Region;
+import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.ArraySet;
import android.util.Log;
import android.view.Choreographer;
import android.view.InputDevice;
@@ -81,6 +83,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ConstantItem;
@@ -92,13 +95,13 @@
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarManager;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.uioverrides.flags.FlagsFactory;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.TraceHelper;
@@ -120,12 +123,14 @@
import com.android.quickstep.util.ActiveGestureLog.CompoundString;
import com.android.quickstep.util.AssistStateManager;
import com.android.quickstep.util.AssistUtils;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.wm.shell.back.IBackAnimation;
@@ -135,13 +140,14 @@
import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.recents.IRecentTasks;
+import com.android.wm.shell.shared.IShellTransitions;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.startingsurface.IStartingWindow;
-import com.android.wm.shell.transition.IShellTransitions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -298,9 +304,9 @@
}
@BinderThread
- public void onSystemUiStateChanged(int stateFlags) {
+ public void onSystemUiStateChanged(@SystemUiStateFlags long stateFlags) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
- int lastFlags = tis.mDeviceState.getSystemUiStateFlags();
+ long lastFlags = tis.mDeviceState.getSystemUiStateFlags();
tis.mDeviceState.setSystemUiFlags(stateFlags);
tis.onSystemUiFlagsChanged(lastFlags);
}));
@@ -317,7 +323,7 @@
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
executeForTouchInteractionService(tis -> {
StatefulActivity activity =
- tis.mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+ tis.mOverviewComponentObserver.getActivityInterface().getCreatedContainer();
if (activity != null) {
activity.enterStageSplitFromRunningApp(leftOrTop);
}
@@ -392,6 +398,25 @@
return tis.mTaskbarManager;
}
+ @VisibleForTesting
+ public void injectFakeTrackpadForTesting() {
+ TouchInteractionService tis = mTis.get();
+ if (tis == null) return;
+ tis.mTrackpadsConnected.add(1000);
+ tis.initInputMonitor("tapl testing");
+ }
+
+ @VisibleForTesting
+ public void ejectFakeTrackpadForTesting() {
+ TouchInteractionService tis = mTis.get();
+ if (tis == null) return;
+ tis.mTrackpadsConnected.clear();
+ // This method destroys the current input monitor if set up, and only init a new one
+ // in 3-button mode if {@code mTrackpadsConnected} is not empty. So in other words,
+ // it will destroy the input monitor.
+ tis.initInputMonitor("tapl testing");
+ }
+
/**
* Sets whether a predictive back-to-home animation is in progress in the device state
*/
@@ -442,11 +467,57 @@
/** Refreshes the current overview target. */
public void refreshOverviewTarget() {
- executeForTouchInteractionService(tis -> tis.onOverviewTargetChange(
- tis.mOverviewComponentObserver.isHomeAndOverviewSame()));
+ executeForTouchInteractionService(tis -> {
+ tis.mAllAppsActionManager.onDestroy();
+ tis.onOverviewTargetChange(tis.mOverviewComponentObserver.isHomeAndOverviewSame());
+ });
}
}
+ private final InputManager.InputDeviceListener mInputDeviceListener =
+ new InputManager.InputDeviceListener() {
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ if (isTrackpadDevice(deviceId)) {
+ boolean wasEmpty = mTrackpadsConnected.isEmpty();
+ mTrackpadsConnected.add(deviceId);
+ if (wasEmpty) {
+ update();
+ }
+ }
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ mTrackpadsConnected.remove(deviceId);
+ if (mTrackpadsConnected.isEmpty()) {
+ update();
+ }
+ }
+
+ private void update() {
+ if (mInputMonitorCompat != null && !mTrackpadsConnected.isEmpty()) {
+ // Don't destroy and reinitialize input monitor due to trackpad
+ // connecting when it's already set up.
+ return;
+ }
+ initInputMonitor("onTrackpadConnected()");
+ }
+
+ private boolean isTrackpadDevice(int deviceId) {
+ InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+ if (inputDevice == null) {
+ return false;
+ }
+ return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
+ | InputDevice.SOURCE_TOUCHPAD);
+ }
+ };
+
private static boolean sConnected = false;
private static boolean sIsInitialized = false;
private RotationTouchHelper mRotationTouchHelper;
@@ -466,6 +537,18 @@
private final ScreenOnTracker.ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
+ private final TaskbarNavButtonCallbacks mNavCallbacks = new TaskbarNavButtonCallbacks() {
+ @Override
+ public void onNavigateHome() {
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
+ }
+
+ @Override
+ public void onToggleOverview() {
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+ }
+ };
+
private ActivityManagerWrapper mAM;
private OverviewCommandHelper mOverviewCommandHelper;
private OverviewComponentObserver mOverviewComponentObserver;
@@ -485,6 +568,8 @@
private TaskbarManager mTaskbarManager;
private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
private AllAppsActionManager mAllAppsActionManager;
+ private InputManager mInputManager;
+ private final Set<Integer> mTrackpadsConnected = new ArraySet<>();
@Override
public void onCreate() {
@@ -494,12 +579,20 @@
mMainChoreographer = Choreographer.getInstance();
mAM = ActivityManagerWrapper.getInstance();
mDeviceState = new RecentsAnimationDeviceState(this, true);
+ mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
mAllAppsActionManager = new AllAppsActionManager(
this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent);
- mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager);
- mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
+ mInputManager = getSystemService(InputManager.class);
+ if (ENABLE_TRACKPAD_GESTURE.get()) {
+ mInputManager.registerInputDeviceListener(mInputDeviceListener,
+ UI_HELPER_EXECUTOR.getHandler());
+ int [] inputDevices = mInputManager.getInputDeviceIds();
+ for (int inputDeviceId : inputDevices) {
+ mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
+ }
+ }
+ mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks);
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
- BootAwarePreloader.start(this);
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked);
@@ -525,7 +618,8 @@
private void initInputMonitor(String reason) {
disposeEventHandlers("Initializing input monitor due to: " + reason);
- if (mDeviceState.isButtonNavMode() && !ENABLE_TRACKPAD_GESTURE.get()) {
+ if (mDeviceState.isButtonNavMode() && (!ENABLE_TRACKPAD_GESTURE.get()
+ || mTrackpadsConnected.isEmpty())) {
return;
}
@@ -592,7 +686,7 @@
mAllAppsActionManager.setHomeAndOverviewSame(isHomeAndOverviewSame);
StatefulActivity newOverviewActivity = mOverviewComponentObserver.getActivityInterface()
- .getCreatedActivity();
+ .getCreatedContainer();
if (newOverviewActivity != null) {
mTaskbarManager.setActivity(newOverviewActivity);
}
@@ -600,44 +694,33 @@
}
private PendingIntent createAllAppsPendingIntent() {
- final Intent homeIntent = new Intent(mOverviewComponentObserver.getHomeIntent())
- .setAction(INTENT_ACTION_ALL_APPS_TOGGLE);
-
if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
return new PendingIntent(new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType,
IBinder allowlistToken, IIntentReceiver finishedReceiver,
String requiredPermission, Bundle options) {
- MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllApps(homeIntent));
+ MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllApps());
}
});
} else {
return PendingIntent.getActivity(
this,
GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
- homeIntent,
+ new Intent(mOverviewComponentObserver.getHomeIntent())
+ .setAction(INTENT_ACTION_ALL_APPS_TOGGLE),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
}
@UiThread
- private void onSystemUiFlagsChanged(int lastSysUIFlags) {
+ private void onSystemUiFlagsChanged(@SystemUiStateFlags long lastSysUIFlags) {
if (LockedUserState.get(this).isUserUnlocked()) {
- int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
+ long systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
mOverviewComponentObserver.onSystemUiStateChanged();
mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
-
- int isShadeExpandedFlag =
- SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
- boolean wasExpanded = (lastSysUIFlags & isShadeExpandedFlag) != 0;
- boolean isExpanded = (systemUiStateFlags & isShadeExpandedFlag) != 0;
- if (wasExpanded != isExpanded && isExpanded) {
- // End live tile when expanding the notification panel for the first time from
- // overview.
- mTaskAnimationManager.endLiveTile();
- }
+ mTaskAnimationManager.onSystemUiFlagsChanged(lastSysUIFlags, systemUiStateFlags);
}
}
@@ -663,6 +746,9 @@
mAllAppsActionManager.onDestroy();
+ mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
+ mTrackpadsConnected.clear();
+
mTaskbarManager.destroy();
sConnected = false;
@@ -716,16 +802,22 @@
boolean isHoverActionWithoutConsumer = enableCursorHoverStates()
&& isHoverActionWithoutConsumer(event);
- // TODO(b/285636175): Uncomment this once WM can properly guarantee all animation callbacks
-// if (mTaskAnimationManager.isRecentsAnimationStartPending()
-// && (action == ACTION_DOWN || isHoverActionWithoutConsumer)) {
-// ActiveGestureLog.INSTANCE.addLog(
-// new CompoundString("TIS.onInputEvent: ")
-// .append("Cannot process input event: a recents animation has been ")
-// .append("requested, but hasn't started."),
-// RECENTS_ANIMATION_START_PENDING);
-// return;
-// }
+ if (enableHandleDelayedGestureCallbacks()) {
+ if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
+ mTaskAnimationManager.notifyNewGestureStart();
+ }
+ if (mTaskAnimationManager.shouldIgnoreMotionEvents()) {
+ if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
+ ActiveGestureLog.INSTANCE.addLog(
+ new CompoundString("TIS.onMotionEvent: A new gesture has been ")
+ .append("started, but a previously-requested recents ")
+ .append("animation hasn't started. Ignoring all following ")
+ .append("motion events."),
+ RECENTS_ANIMATION_START_PENDING);
+ }
+ return;
+ }
+ }
SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
@@ -751,7 +843,6 @@
GestureState prevGestureState = new GestureState(mGestureState);
GestureState newGestureState = createGestureState(mGestureState,
getTrackpadGestureType(event));
- newGestureState.setSwipeUpStartTimeMs(SystemClock.uptimeMillis());
mConsumer.onConsumerAboutToBeSwitched();
mGestureState = newGestureState;
mConsumer = newConsumer(prevGestureState, mGestureState, event);
@@ -828,8 +919,8 @@
}
}
- boolean cancelGesture = mGestureState.getActivityInterface() != null
- && mGestureState.getActivityInterface().shouldCancelCurrentGesture();
+ boolean cancelGesture = mGestureState.getContainerInterface() != null
+ && mGestureState.getContainerInterface().shouldCancelCurrentGesture();
boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL || cancelGesture)
&& mConsumer != null
&& !mConsumer.getActiveConsumerInHierarchy().isConsumerDetachedFromGesture();
@@ -999,7 +1090,7 @@
.append("TaskbarActivityContext != null, ")
.append("using TaskbarUnstashInputConsumer");
base = new TaskbarUnstashInputConsumer(this, base, mInputMonitorCompat, tac,
- mOverviewCommandHelper);
+ mOverviewCommandHelper, mGestureState);
}
}
if (enableBubblesLongPressNavHandle()) {
@@ -1027,7 +1118,7 @@
}
reasonString.append("using NavHandleLongPressInputConsumer");
base = new NavHandleLongPressInputConsumer(this, base, mInputMonitorCompat,
- mDeviceState, navHandle);
+ mDeviceState, navHandle, mGestureState);
}
if (!enableBubblesLongPressNavHandle()) {
@@ -1146,7 +1237,7 @@
TopTaskTracker.CachedTaskInfo runningTask = gestureState.getRunningTask();
// Use overview input consumer for sharesheets on top of home.
- boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
+ boolean forceOverviewInputConsumer = gestureState.getContainerInterface().isStarted()
&& runningTask != null
&& runningTask.isRootChooseActivity();
@@ -1154,7 +1245,7 @@
// running activity as the task behind the overlay.
TopTaskTracker.CachedTaskInfo otherVisibleTask = runningTask == null
? null
- : runningTask.otherVisibleTaskThisIsExcludedOver();
+ : runningTask.getVisibleNonExcludedTask();
if (otherVisibleTask != null) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("Changing active task to ")
.append(otherVisibleTask.getPackageName())
@@ -1170,7 +1261,7 @@
// with shell-transitions, home is resumed during recents animation, so
// explicitly check against recents animation too.
boolean launcherResumedThroughShellTransition =
- gestureState.getActivityInterface().isResumed()
+ gestureState.getContainerInterface().isResumed()
&& !previousGestureState.isRecentsAnimationRunning();
// If a task fragment within Launcher is resumed
boolean launcherChildActivityResumed = useActivityOverlay()
@@ -1180,7 +1271,7 @@
&& !launcherResumedThroughShellTransition
&& !previousGestureState.isRecentsAnimationRunning();
- if (gestureState.getActivityInterface().isInLiveTileMode()) {
+ if (gestureState.getContainerInterface().isInLiveTileMode()) {
return createOverviewInputConsumer(
previousGestureState,
gestureState,
@@ -1228,7 +1319,7 @@
final AbsSwipeUpHandler.Factory factory = getSwipeUpHandlerFactory();
final boolean shouldDefer = !mOverviewComponentObserver.isHomeAndOverviewSame()
- || gestureState.getActivityInterface().deferStartingActivity(mDeviceState, event);
+ || gestureState.getContainerInterface().deferStartingActivity(mDeviceState, event);
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
return new OtherActivityInputConsumer(this, mDeviceState, mTaskAnimationManager,
gestureState, shouldDefer, this::onConsumerInactive,
@@ -1262,18 +1353,19 @@
MotionEvent event,
boolean forceOverviewInputConsumer,
CompoundString reasonString) {
- StatefulActivity activity = gestureState.getActivityInterface().getCreatedActivity();
- if (activity == null) {
+ RecentsViewContainer container = gestureState.getContainerInterface().getCreatedContainer();
+ if (container == null) {
return getDefaultInputConsumer(
reasonString.append(SUBSTRING_PREFIX)
.append("activity == null, trying to use default input consumer"));
}
- boolean hasWindowFocus = activity.getRootView().hasWindowFocus();
+ boolean hasWindowFocus = container.getRootView().hasWindowFocus();
boolean isPreviousGestureAnimatingToLauncher =
previousGestureState.isRunningAnimationToLauncher()
|| mDeviceState.isPredictiveBackToHomeInProgress();
- boolean isInLiveTileMode = gestureState.getActivityInterface().isInLiveTileMode();
+ boolean isInLiveTileMode = gestureState.getContainerInterface().isInLiveTileMode();
+
reasonString.append(SUBSTRING_PREFIX)
.append(hasWindowFocus
? "activity has window focus"
@@ -1287,14 +1379,14 @@
|| isInLiveTileMode) {
reasonString.append(SUBSTRING_PREFIX)
.append("overview should have focus, using OverviewInputConsumer");
- return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
+ return new OverviewInputConsumer(gestureState, container, mInputMonitorCompat,
false /* startingInActivityBounds */);
} else {
reasonString.append(SUBSTRING_PREFIX).append(
"overview shouldn't have focus, using OverviewWithoutFocusInputConsumer");
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
- return new OverviewWithoutFocusInputConsumer(activity, mDeviceState, gestureState,
- mInputMonitorCompat, disableHorizontalSwipe);
+ return new OverviewWithoutFocusInputConsumer(container.asContext(), mDeviceState,
+ gestureState, mInputMonitorCompat, disableHorizontalSwipe);
}
}
@@ -1367,7 +1459,7 @@
mOverviewComponentObserver.getActivityInterface();
final Intent overviewIntent = new Intent(
mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
- if (activityInterface.getCreatedActivity() != null && fromInit) {
+ if (activityInterface.getCreatedContainer() != null && fromInit) {
// The activity has been created before the initialization of overview service. It is
// usually happens when booting or launcher is the top activity, so we should already
// have the latest state.
@@ -1389,7 +1481,7 @@
}
final BaseActivityInterface activityInterface =
mOverviewComponentObserver.getActivityInterface();
- final BaseDraggingActivity activity = activityInterface.getCreatedActivity();
+ final BaseDraggingActivity activity = activityInterface.getCreatedContainer();
if (activity == null || activity.isStarted()) {
// We only care about the existing background activity.
return;
@@ -1419,7 +1511,6 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
// Dump everything
- FlagsFactory.dump(pw);
if (LockedUserState.get(this).isUserUnlocked()) {
PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
}
@@ -1431,29 +1522,34 @@
mOverviewCommandHelper.dump(pw);
}
if (mGestureState != null) {
- mGestureState.dump(pw);
+ mGestureState.dump("", pw);
}
pw.println("Input state:");
- pw.println(" mInputMonitorCompat=" + mInputMonitorCompat);
- pw.println(" mInputEventReceiver=" + mInputEventReceiver);
+ pw.println("\tmInputMonitorCompat=" + mInputMonitorCompat);
+ pw.println("\tmInputEventReceiver=" + mInputEventReceiver);
DisplayController.INSTANCE.get(this).dump(pw);
pw.println("TouchState:");
BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
- : mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+ : mOverviewComponentObserver.getActivityInterface().getCreatedContainer();
boolean resumed = mOverviewComponentObserver != null
&& mOverviewComponentObserver.getActivityInterface().isResumed();
- pw.println(" createdOverviewActivity=" + createdOverviewActivity);
- pw.println(" resumed=" + resumed);
- pw.println(" mConsumer=" + mConsumer.getName());
+ pw.println("\tcreatedOverviewActivity=" + createdOverviewActivity);
+ pw.println("\tresumed=" + resumed);
+ pw.println("\tmConsumer=" + mConsumer.getName());
ActiveGestureLog.INSTANCE.dump("", pw);
RecentsModel.INSTANCE.get(this).dump("", pw);
+ TopTaskTracker.INSTANCE.get(this).dump("", pw);
+ if (mTaskAnimationManager != null) {
+ mTaskAnimationManager.dump("", pw);
+ }
if (createdOverviewActivity != null) {
createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
}
mTaskbarManager.dumpLogs("", pw);
pw.println("AssistStateManager:");
- AssistStateManager.INSTANCE.get(this).dump(" ", pw);
+ AssistStateManager.INSTANCE.get(this).dump("\t", pw);
SystemUiProxy.INSTANCE.get(this).dump(pw);
+ DeviceConfigWrapper.get().dump(" ", pw);
}
private AbsSwipeUpHandler createLauncherSwipeHandler(
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
index 8a87f63..ec531d8 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -24,9 +24,9 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.TouchController;
-import com.android.quickstep.RecentsActivity;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+import com.android.quickstep.views.RecentsViewContainer;
/**
* In 0-button mode, intercepts swipe up from the nav bar on FallbackRecentsView to go home.
@@ -34,19 +34,19 @@
public class FallbackNavBarTouchController implements TouchController,
TriggerSwipeUpTouchTracker.OnSwipeUpListener {
- private final RecentsActivity mActivity;
+ private final RecentsViewContainer mContainer;
@Nullable
private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
- public FallbackNavBarTouchController(RecentsActivity activity) {
- mActivity = activity;
- NavigationMode sysUINavigationMode = DisplayController.getNavigationMode(mActivity);
+ public FallbackNavBarTouchController(RecentsViewContainer container) {
+ mContainer = container;
+ NavigationMode sysUINavigationMode =
+ DisplayController.getNavigationMode(mContainer.asContext());
if (sysUINavigationMode == NavigationMode.NO_BUTTON) {
NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
- DisplayController.INSTANCE.get(mActivity).getInfo());
- mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
- true /* disableHorizontalSwipe */, navBarPosition,
- null /* onInterceptTouch */, this);
+ DisplayController.INSTANCE.get(mContainer.asContext()).getInfo());
+ mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mContainer.asContext(),
+ true /* disableHorizontalSwipe */, navBarPosition, this);
} else {
mTriggerSwipeUpTracker = null;
}
@@ -76,9 +76,6 @@
@Override
public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
- mActivity.<FallbackRecentsView>getOverviewPanel().startHome();
+ mContainer.<FallbackRecentsView>getOverviewPanel().startHome();
}
-
- @Override
- public void onSwipeUpCancelled() {}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 3e731e5..94764a5 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -42,13 +42,14 @@
import androidx.annotation.NonNull;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.MultiPropertyFactory;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.views.ClearAllButton;
+import com.android.quickstep.views.RecentsView;
/**
* State controller for fallback recents activity
@@ -96,7 +97,7 @@
clearAllButtonAlpha, LINEAR);
float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
- MultiPropertyFactory.MULTI_PROPERTY_VALUE, overviewButtonAlpha, LINEAR);
+ AnimatedFloat.VALUE, overviewButtonAlpha, LINEAR);
float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
@@ -125,7 +126,7 @@
setter.add(pa.buildAnim());
}
- Pair<FloatProperty, FloatProperty> taskViewsFloat =
+ Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat =
mRecentsView.getPagedOrientationHandler().getSplitSelectTaskOffset(
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
mActivity.getDeviceProfile());
@@ -135,7 +136,7 @@
}
private Interpolator getOverviewInterpolator(RecentsState toState) {
- return toState.overviewUi() ? INSTANT : FINAL_FRAME;
+ return toState.isRecentsViewVisible() ? INSTANT : FINAL_FRAME;
}
/**
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 0ee50a4..f4a2738 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -36,6 +36,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
@@ -52,6 +53,8 @@
import com.android.systemui.shared.recents.model.Task;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsState>
implements StateListener<RecentsState> {
@@ -67,7 +70,7 @@
public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr, FallbackActivityInterface.INSTANCE);
- mActivity.getStateManager().addStateListener(this);
+ mContainer.getStateManager().addStateListener(this);
}
@Override
@@ -80,13 +83,18 @@
@Override
protected void handleStartHome(boolean animated) {
- mActivity.startHome();
- AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
+ mContainer.startHome();
+ AbstractFloatingView.closeAllOpenViews(mContainer, mContainer.isStarted());
}
@Override
protected boolean canStartHomeSafely() {
- return mActivity.canStartHomeSafely();
+ return mContainer.canStartHomeSafely();
+ }
+
+ @Override
+ public StateManager<RecentsState, RecentsActivity> getStateManager() {
+ return mContainer.getStateManager();
}
/**
@@ -138,8 +146,9 @@
@Override
public void setCurrentTask(int runningTaskViewId) {
super.setCurrentTask(runningTaskViewId);
- int runningTaskId = getTaskIdsForRunningTaskView()[0];
- if (mHomeTask != null && mHomeTask.key.id != runningTaskId) {
+ int[] runningTaskIds = getTaskIdsForRunningTaskView();
+ if (mHomeTask != null
+ && Arrays.stream(runningTaskIds).noneMatch(taskId -> taskId == mHomeTask.key.id)) {
mHomeTask = null;
setRunningTaskHidden(false);
}
@@ -171,18 +180,19 @@
}
@Override
- protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {
+ protected void applyLoadPlan(List<GroupTask> taskGroups) {
// When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher
// as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
// track the index of the next task appropriately, as if we are switching on any other app.
// TODO(b/195607777) Confirm home task info is front-most task and not mixed in with others
- int runningTaskId = getTaskIdsForRunningTaskView()[0];
- if (mHomeTask != null && mHomeTask.key.id == runningTaskId
+ int[] runningTaskIds = getTaskIdsForRunningTaskView();
+ if (mHomeTask != null
+ && Arrays.stream(runningTaskIds).allMatch(taskId -> taskId == mHomeTask.key.id)
&& !taskGroups.isEmpty()) {
// Check if the task list has running task
boolean found = false;
for (GroupTask group : taskGroups) {
- if (group.containsTask(runningTaskId)) {
+ if (Arrays.stream(runningTaskIds).allMatch(group::containsTask)) {
found = true;
break;
}
@@ -210,10 +220,10 @@
public void setModalStateEnabled(int taskId, boolean animate) {
if (taskId != INVALID_TASK_ID) {
setSelectedTask(taskId);
- mActivity.getStateManager().goToState(RecentsState.MODAL_TASK, animate);
+ mContainer.getStateManager().goToState(RecentsState.MODAL_TASK, animate);
} else {
- if (mActivity.isInState(RecentsState.MODAL_TASK)) {
- mActivity.getStateManager().goToState(DEFAULT, animate);
+ if (mContainer.isInState(RecentsState.MODAL_TASK)) {
+ mContainer.getStateManager().goToState(DEFAULT, animate);
}
}
}
@@ -223,13 +233,13 @@
@SplitConfigurationOptions.StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
super.initiateSplitSelect(taskView, stagePosition, splitEvent);
- mActivity.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
+ mContainer.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
}
@Override
public void onStateTransitionStart(RecentsState toState) {
setOverviewStateEnabled(true);
- setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+ setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.isFullScreen());
if (toState == MODAL_TASK) {
setOverviewSelectEnabled(true);
@@ -238,7 +248,7 @@
}
// Set border after select mode changes to avoid showing border during state transition
- if (!toState.overviewUi() || toState == MODAL_TASK) {
+ if (!toState.isRecentsViewVisible() || toState == MODAL_TASK) {
setTaskBorderEnabled(false);
}
@@ -258,7 +268,7 @@
setOverviewSelectEnabled(false);
}
- if (finalState.overviewUi() && finalState != MODAL_TASK) {
+ if (finalState.isRecentsViewVisible() && finalState != MODAL_TASK) {
setTaskBorderEnabled(true);
}
@@ -280,7 +290,7 @@
public void setOverviewStateEnabled(boolean enabled) {
super.setOverviewStateEnabled(enabled);
if (enabled) {
- RecentsState state = mActivity.getStateManager().getState();
+ RecentsState state = mContainer.getStateManager().getState();
setDisallowScrollToClearAll(!state.hasClearAllButton());
}
}
@@ -289,17 +299,23 @@
public boolean onTouchEvent(MotionEvent ev) {
boolean result = super.onTouchEvent(ev);
// Do not let touch escape to siblings below this view.
- return result || mActivity.getStateManager().getState().overviewUi();
+ return result || mContainer.getStateManager().getState().isRecentsViewVisible();
}
@Override
public void initiateSplitSelect(SplitSelectSource splitSelectSource) {
super.initiateSplitSelect(splitSelectSource);
- mActivity.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
+ mContainer.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
}
@Override
protected boolean canLaunchFullscreenTask() {
- return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
+ return !mContainer.isInState(OVERVIEW_SPLIT_SELECT);
+ }
+
+ /** Returns if app pairs are supported in this launcher. */
+ @Override
+ public boolean supportsAppPairs() {
+ return false;
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 8b5f091..ca9753f 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -26,7 +26,7 @@
import com.android.launcher3.R;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.util.Themes;
-import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.views.RecentsViewContainer;
/**
* State definition for Fallback recents
@@ -40,22 +40,23 @@
private static final int FLAG_SHOW_AS_GRID = BaseState.getFlag(4);
private static final int FLAG_SCRIM = BaseState.getFlag(5);
private static final int FLAG_LIVE_TILE = BaseState.getFlag(6);
- private static final int FLAG_OVERVIEW_UI = BaseState.getFlag(7);
+ private static final int FLAG_RECENTS_VIEW_VISIBLE = BaseState.getFlag(7);
private static final int FLAG_TASK_THUMBNAIL_SPLASH = BaseState.getFlag(8);
public static final RecentsState DEFAULT = new RecentsState(0,
FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID
- | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_OVERVIEW_UI);
+ | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
public static final RecentsState MODAL_TASK = new ModalState(1,
FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
- | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_OVERVIEW_UI);
+ | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
- FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN | FLAG_OVERVIEW_UI
+ FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN
+ | FLAG_RECENTS_VIEW_VISIBLE
| FLAG_TASK_THUMBNAIL_SPLASH);
public static final RecentsState HOME = new RecentsState(3, 0);
public static final RecentsState BG_LAUNCHER = new LauncherState(4, 0);
public static final RecentsState OVERVIEW_SPLIT_SELECT = new RecentsState(5,
- FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_OVERVIEW_UI | FLAG_CLOSE_POPUPS
+ FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_RECENTS_VIEW_VISIBLE | FLAG_CLOSE_POPUPS
| FLAG_DISABLE_RESTORE);
public final int ordinal;
@@ -126,12 +127,13 @@
/**
* For this state, what color scrim should be drawn behind overview.
*/
- public int getScrimColor(RecentsActivity activity) {
- return hasFlag(FLAG_SCRIM) ? Themes.getAttrColor(activity, R.attr.overviewScrimColor)
+ public int getScrimColor(Context context) {
+ return hasFlag(FLAG_SCRIM)
+ ? Themes.getAttrColor(context, R.attr.overviewScrimColor)
: Color.TRANSPARENT;
}
- public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
+ public float[] getOverviewScaleAndOffset(RecentsViewContainer container) {
return new float[] { NO_SCALE, NO_OFFSET };
}
@@ -150,8 +152,8 @@
/**
* True if the state has overview panel visible.
*/
- public boolean overviewUi() {
- return hasFlag(FLAG_OVERVIEW_UI);
+ public boolean isRecentsViewVisible() {
+ return hasFlag(FLAG_RECENTS_VIEW_VISIBLE);
}
private static class ModalState extends RecentsState {
@@ -161,8 +163,8 @@
}
@Override
- public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
- return getOverviewScaleAndOffsetForModalState(activity);
+ public float[] getOverviewScaleAndOffset(RecentsViewContainer container) {
+ return getOverviewScaleAndOffsetForModalState(container.getOverviewPanel());
}
}
@@ -172,8 +174,8 @@
}
@Override
- public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
- return getOverviewScaleAndOffsetForBackgroundState(activity);
+ public float[] getOverviewScaleAndOffset(RecentsViewContainer container) {
+ return getOverviewScaleAndOffsetForBackgroundState(container.getOverviewPanel());
}
}
@@ -183,7 +185,7 @@
}
@Override
- public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
+ public float[] getOverviewScaleAndOffset(RecentsViewContainer container) {
return new float[] { NO_SCALE, 1 };
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
index db4927a..2cb398c 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
@@ -26,7 +26,7 @@
@Override
protected boolean isRecentsInteractive() {
- return mActivity.hasWindowFocus() || mActivity.getStateManager().getState().hasLiveTile();
+ return mContainer.hasWindowFocus() || mContainer.getStateManager().getState().hasLiveTile();
}
@Override
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index ba012c9..222ccd3 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -43,13 +43,13 @@
import android.view.ViewConfiguration;
import com.android.app.animation.Interpolators;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
-import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.system.InputMonitorCompat;
import java.util.function.Consumer;
@@ -78,7 +78,7 @@
private float mTimeFraction;
private long mDragTime;
private float mLastProgress;
- private BaseActivityInterface mActivityInterface;
+ private BaseContainerInterface mContainerInterface;
private final float mDragDistThreshold;
private final float mFlingDistThreshold;
@@ -106,7 +106,7 @@
float slop = ViewConfiguration.get(context).getScaledTouchSlop();
mSquaredSlop = slop * slop;
- mActivityInterface = gestureState.getActivityInterface();
+ mContainerInterface = gestureState.getContainerInterface();
boolean flingDisabled = deviceState.isAssistantGestureIsConstrained()
|| deviceState.isInDeferredGestureRegion(startEvent);
@@ -237,9 +237,9 @@
}
private void startAssistantInternal() {
- BaseDraggingActivity launcherActivity = mActivityInterface.getCreatedActivity();
- if (launcherActivity != null) {
- launcherActivity.getRootView().performHapticFeedback(
+ RecentsViewContainer container = mContainerInterface.getCreatedContainer();
+ if (container != null) {
+ container.getRootView().performHapticFeedback(
13, // HapticFeedbackConstants.GESTURE_END
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index cf8750f..848a43a 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -20,18 +20,23 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_NAVBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_STASHED_TASKBAR;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.LogConfig.NAV_HANDLE_LONG_PRESS;
import android.content.Context;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.DeviceConfigWrapper;
+import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.NavHandle;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.TopTaskTracker;
+import com.android.quickstep.util.AssistStateManager;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
@@ -39,33 +44,54 @@
*/
public class NavHandleLongPressInputConsumer extends DelegateInputConsumer {
+ private static final String TAG = "NavHandleLongPressIC";
+ private static final boolean DEBUG_NAV_HANDLE = Utilities.isPropertyEnabled(
+ NAV_HANDLE_LONG_PRESS);
+
private final NavHandleLongPressHandler mNavHandleLongPressHandler;
private final float mNavHandleWidth;
private final float mScreenWidth;
private final Runnable mTriggerLongPress = this::triggerLongPress;
- private final float mTouchSlopSquared;
+ private final float mTouchSlopSquaredOriginal;
+ private float mTouchSlopSquared;
+ private final float mOuterTouchSlopSquared;
private final int mLongPressTimeout;
+ private final int mOuterLongPressTimeout;
private final boolean mDeepPressEnabled;
private final NavHandle mNavHandle;
private final StatsLogManager mStatsLogManager;
private final TopTaskTracker mTopTaskTracker;
+ private final GestureState mGestureState;
private MotionEvent mCurrentDownEvent;
private boolean mDeepPressLogged; // Whether deep press has been logged for the current touch.
public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
InputMonitorCompat inputMonitor, RecentsAnimationDeviceState deviceState,
- NavHandle navHandle) {
+ NavHandle navHandle, GestureState gestureState) {
super(delegate, inputMonitor);
mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
- mDeepPressEnabled = FeatureFlags.ENABLE_LPNH_DEEP_PRESS.get();
- if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
- mLongPressTimeout = FeatureFlags.LPNH_TIMEOUT_MS.get();
+ mDeepPressEnabled = DeviceConfigWrapper.get().getEnableLpnhDeepPress();
+ int twoStageMultiplier = DeviceConfigWrapper.get().getTwoStageMultiplier();
+ AssistStateManager assistStateManager = AssistStateManager.INSTANCE.get(context);
+ if (assistStateManager.getLPNHDurationMillis().isPresent()) {
+ mLongPressTimeout = assistStateManager.getLPNHDurationMillis().get().intValue();
} else {
mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
}
- mTouchSlopSquared = deviceState.getSquaredTouchSlop();
+ mOuterLongPressTimeout = mLongPressTimeout * twoStageMultiplier;
+ mTouchSlopSquaredOriginal = deviceState.getSquaredTouchSlop();
+ mTouchSlopSquared = mTouchSlopSquaredOriginal;
+ mOuterTouchSlopSquared = mTouchSlopSquared * (twoStageMultiplier * twoStageMultiplier);
+ mGestureState = gestureState;
+ mGestureState.setIsInExtendedSlopRegion(false);
+ if (DEBUG_NAV_HANDLE) {
+ Log.d(TAG, "mLongPressTimeout=" + mLongPressTimeout);
+ Log.d(TAG, "mOuterLongPressTimeout=" + mOuterLongPressTimeout);
+ Log.d(TAG, "mTouchSlopSquared=" + mTouchSlopSquared);
+ Log.d(TAG, "mOuterTouchSlopSquared=" + mOuterTouchSlopSquared);
+ }
mNavHandle = navHandle;
mNavHandleWidth = navHandle.getNavHandleWidth(context);
mNavHandleLongPressHandler = NavHandleLongPressHandler.newInstance(context);
@@ -103,22 +129,46 @@
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
+ mTouchSlopSquared = mTouchSlopSquaredOriginal;
+ mGestureState.setIsInExtendedSlopRegion(false);
mDeepPressLogged = false;
if (isInNavBarHorizontalArea(ev.getRawX())) {
mNavHandleLongPressHandler.onTouchStarted(mNavHandle);
MAIN_EXECUTOR.getHandler().postDelayed(mTriggerLongPress, mLongPressTimeout);
}
+ if (DEBUG_NAV_HANDLE) {
+ Log.d(TAG, "ACTION_DOWN");
+ }
}
case MotionEvent.ACTION_MOVE -> {
if (!MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)) {
break;
}
- float touchSlopSquared = mTouchSlopSquared;
float dx = ev.getX() - mCurrentDownEvent.getX();
float dy = ev.getY() - mCurrentDownEvent.getY();
double distanceSquared = (dx * dx) + (dy * dy);
- if (distanceSquared > touchSlopSquared) {
+ if (DEBUG_NAV_HANDLE) {
+ Log.d(TAG, "ACTION_MOVE distanceSquared=" + distanceSquared);
+ }
+ if (DeviceConfigWrapper.get().getEnableLpnhTwoStages()) {
+ if (mTouchSlopSquared < distanceSquared
+ && distanceSquared <= mOuterTouchSlopSquared) {
+ MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerLongPress);
+ int delay = mOuterLongPressTimeout
+ - (int) (ev.getEventTime() - ev.getDownTime());
+ MAIN_EXECUTOR.getHandler().postDelayed(mTriggerLongPress, delay);
+ mTouchSlopSquared = mOuterTouchSlopSquared;
+ mGestureState.setIsInExtendedSlopRegion(true);
+ if (DEBUG_NAV_HANDLE) {
+ Log.d(TAG, "Touch in middle region!");
+ }
+ }
+ }
+ if (distanceSquared > mTouchSlopSquared) {
+ if (DEBUG_NAV_HANDLE) {
+ Log.d(TAG, "Touch slop out. mTouchSlopSquared=" + mTouchSlopSquared);
+ }
cancelLongPress("touch slop passed");
}
}
@@ -147,6 +197,9 @@
}
private void triggerLongPress() {
+ if (DEBUG_NAV_HANDLE) {
+ Log.d(TAG, "triggerLongPress");
+ }
String runningPackage = mTopTaskTracker.getCachedTopTask(
/* filterOnlyVisibleRecents */ true).getPackageName();
mStatsLogManager.logger().withPackageName(runningPackage).log(
@@ -169,12 +222,25 @@
}
private void cancelLongPress(String reason) {
+ if (DEBUG_NAV_HANDLE) {
+ Log.d(TAG, "cancelLongPress");
+ }
+ mGestureState.setIsInExtendedSlopRegion(false);
MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerLongPress);
mNavHandleLongPressHandler.onTouchFinished(mNavHandle, reason);
}
private boolean isInNavBarHorizontalArea(float x) {
float areaFromMiddle = mNavHandleWidth / 2.0f;
+ if (DeviceConfigWrapper.get().getCustomLpnhThresholds()) {
+ areaFromMiddle += Utilities.dpToPx(
+ DeviceConfigWrapper.get().getLpnhExtraTouchWidthDp());
+ }
+ int minAccessibleSize = Utilities.dpToPx(24); // Half of 48dp because this is per side.
+ if (areaFromMiddle < minAccessibleSize) {
+ Log.w(TAG, "Custom nav handle region is too small - resetting to 48dp");
+ areaFromMiddle = minAccessibleSize;
+ }
float distFromMiddle = Math.abs(mScreenWidth / 2.0f - x);
return distFromMiddle < areaFromMiddle;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 0f8ceba..0d450c6 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -49,7 +49,6 @@
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.AbsSwipeUpHandler;
import com.android.quickstep.AbsSwipeUpHandler.Factory;
-import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.RecentsAnimationCallbacks;
@@ -86,8 +85,6 @@
private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
private final InputMonitorCompat mInputMonitorCompat;
private final InputEventReceiver mInputEventReceiver;
- private final BaseActivityInterface mActivityInterface;
-
private final AbsSwipeUpHandler.Factory mHandlerFactory;
private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
@@ -135,7 +132,6 @@
mTaskAnimationManager = taskAnimationManager;
mGestureState = gestureState;
mHandlerFactory = handlerFactory;
- mActivityInterface = mGestureState.getActivityInterface();
mMotionPauseDetector = new MotionPauseDetector(base, false,
mNavBarPosition.isLeftEdge() || mNavBarPosition.isRightEdge()
@@ -288,8 +284,9 @@
float horizontalDist = Math.abs(displacementX);
float upDist = -displacement;
- boolean passedSlop = mGestureState.isTrackpadGesture() || squaredHypot(
- displacementX, displacementY) >= mSquaredTouchSlop;
+ boolean passedSlop = mGestureState.isTrackpadGesture()
+ || (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop
+ && !mGestureState.isInExtendedSlopRegion());
if (!mPassedSlopOnThisGesture && passedSlop) {
mPassedSlopOnThisGesture = true;
@@ -394,7 +391,8 @@
mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs);
mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler.getMotionPauseListener());
- mInteractionHandler.initWhenReady();
+ mInteractionHandler.initWhenReady(
+ "OtherActivityInputConsumer.startTouchTrackingForWindowAnimation");
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 7d3a860..c61f71d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -28,26 +28,27 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.statemanager.BaseState;
-import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.TaskUtils;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
* Input consumer for handling touch on the recents/Launcher activity.
*/
-public class OverviewInputConsumer<S extends BaseState<S>, T extends StatefulActivity<S>>
+public class OverviewInputConsumer<S extends BaseState<S>, T extends RecentsViewContainer>
implements InputConsumer {
- private final T mActivity;
- private final BaseActivityInterface<?, T> mActivityInterface;
+ private final T mContainer;
+ private final BaseContainerInterface<?, T> mContainerInterface;
private final BaseDragLayer mTarget;
private final InputMonitorCompat mInputMonitor;
+ private final GestureState mGestureState;
private final int[] mLocationOnScreen = new int[2];
@@ -56,14 +57,15 @@
private boolean mHasSetTouchModeForFirstDPadEvent;
private boolean mIsWaitingForAttachToWindow;
- public OverviewInputConsumer(GestureState gestureState, T activity,
+ public OverviewInputConsumer(GestureState gestureState, T container,
@Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
- mActivity = activity;
+ mContainer = container;
mInputMonitor = inputMonitor;
mStartingInActivityBounds = startingInActivityBounds;
- mActivityInterface = gestureState.getActivityInterface();
+ mContainerInterface = gestureState.getContainerInterface();
+ mGestureState = gestureState;
- mTarget = activity.getDragLayer();
+ mTarget = container.getDragLayer();
mTarget.getLocationOnScreen(mLocationOnScreen);
}
@@ -84,14 +86,17 @@
ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
}
ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
- boolean handled = mTarget.proxyTouchEvent(ev, mStartingInActivityBounds);
+ boolean handled = false;
+ if (mGestureState == null || !mGestureState.isInExtendedSlopRegion()) {
+ handled = mTarget.proxyTouchEvent(ev, mStartingInActivityBounds);
+ }
ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
ev.setEdgeFlags(flags);
if (!mTargetHandledTouch && handled) {
mTargetHandledTouch = true;
if (!mStartingInActivityBounds) {
- mActivityInterface.closeOverlay();
+ mContainerInterface.closeOverlay();
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
}
if (mInputMonitor != null) {
@@ -100,13 +105,13 @@
}
}
if (mHasSetTouchModeForFirstDPadEvent) {
- mActivity.getRootView().clearFocus();
+ mContainer.getRootView().clearFocus();
}
}
@Override
public void onHoverEvent(MotionEvent ev) {
- mActivity.dispatchGenericMotionEvent(ev);
+ mContainer.dispatchGenericMotionEvent(ev);
}
@Override
@@ -115,7 +120,8 @@
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE:
- MediaSessionManager mgr = mActivity.getSystemService(MediaSessionManager.class);
+ MediaSessionManager mgr = mContainer.asContext()
+ .getSystemService(MediaSessionManager.class);
mgr.dispatchVolumeKeyEventAsSystemService(ev,
AudioManager.USE_DEFAULT_STREAM_TYPE);
break;
@@ -124,7 +130,7 @@
if (mHasSetTouchModeForFirstDPadEvent) {
break;
}
- View viewRoot = mActivity.getRootView();
+ View viewRoot = mContainer.getRootView();
if (viewRoot.isAttachedToWindow()) {
setTouchModeChanged(viewRoot);
break;
@@ -150,7 +156,7 @@
default:
break;
}
- mActivity.dispatchKeyEvent(ev);
+ mContainer.dispatchKeyEvent(ev);
}
private void setTouchModeChanged(@NonNull View viewRoot) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 41730bb..42e8694 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -51,7 +51,7 @@
mGestureState = gestureState;
mInputMonitor = inputMonitor;
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, disableHorizontalSwipe,
- deviceState.getNavBarPosition(), this::onInterceptTouch, this);
+ deviceState.getNavBarPosition(), this);
}
@Override
@@ -69,7 +69,8 @@
mTriggerSwipeUpTracker.onMotionEvent(ev);
}
- private void onInterceptTouch() {
+ @Override
+ public void onSwipeUpTouchIntercepted() {
if (mInputMonitor != null) {
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
mInputMonitor.pilferPointers();
@@ -93,7 +94,4 @@
.build())
.log(LAUNCHER_HOME_GESTURE);
}
-
- @Override
- public void onSwipeUpCancelled() {}
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
index a8bf333..d73c23f 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
@@ -19,12 +19,12 @@
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.views.RecentsViewContainer;
/**
* An input consumer that detects swipe up and hold to exit screen pinning mode.
@@ -44,10 +44,10 @@
mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/);
mMotionPauseDetector.setOnMotionPauseListener(() -> {
SystemUiProxy.INSTANCE.get(context).stopScreenPinning();
- BaseDraggingActivity launcherActivity = gestureState.getActivityInterface()
- .getCreatedActivity();
- if (launcherActivity != null) {
- launcherActivity.getRootView().performHapticFeedback(
+ RecentsViewContainer container = gestureState.getContainerInterface()
+ .getCreatedContainer();
+ if (container != null) {
+ container.getRootView().performHapticFeedback(
HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
index 4806ac1..871d075 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
@@ -54,7 +54,7 @@
mContext = context;
mInputMonitor = inputMonitor;
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, true,
- deviceState.getNavBarPosition(), this::onInterceptTouch, this);
+ deviceState.getNavBarPosition(), this);
}
@Override
@@ -72,7 +72,8 @@
mTriggerSwipeUpTracker.onMotionEvent(ev);
}
- private void onInterceptTouch() {
+ @Override
+ public void onSwipeUpTouchIntercepted() {
if (mInputMonitor != null) {
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
mInputMonitor.pilferPointers();
@@ -88,9 +89,4 @@
Log.e(TAG, "Exception calling closeSystemDialogs " + e.getMessage());
}
}
-
- @Override
- public void onSwipeUpCancelled() {
-
- }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index cd180ba..95295b0 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -21,6 +21,7 @@
import static android.view.MotionEvent.INVALID_POINTER_ID;
import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TOUCHING;
@@ -28,8 +29,11 @@
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
import android.view.InputDevice;
import android.view.MotionEvent;
+import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import androidx.annotation.Nullable;
@@ -42,6 +46,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.OverviewCommandHelper;
import com.android.systemui.shared.system.InputMonitorCompat;
@@ -51,6 +56,12 @@
*/
public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
+ private static final int HOVER_TASKBAR_UNSTASH_TIMEOUT = 500;
+
+ private static final int NUM_MOTION_MOVE_THRESHOLD = 3;
+
+ private static final Handler sUnstashHandler = new Handler(Looper.getMainLooper());
+
private final TaskbarActivityContext mTaskbarActivityContext;
private final OverviewCommandHelper mOverviewCommandHelper;
private final float mUnstashArea;
@@ -76,10 +87,16 @@
private final int mStashedTaskbarBottomEdge;
private final @Nullable TransitionCallback mTransitionCallback;
+ private final GestureState mGestureState;
+ private VelocityTracker mVelocityTracker;
+ private boolean mCanPlayTaskbarBgAlphaAnimation = true;
+ private int mMotionMoveCount = 0;
+ // Velocity defined as dp per s
+ private float mTaskbarSlowVelocityYThreshold;
public TaskbarUnstashInputConsumer(Context context, InputConsumer delegate,
InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext,
- OverviewCommandHelper overviewCommandHelper) {
+ OverviewCommandHelper overviewCommandHelper, GestureState gestureState) {
super(delegate, inputMonitor);
mTaskbarActivityContext = taskbarActivityContext;
mOverviewCommandHelper = overviewCommandHelper;
@@ -94,6 +111,8 @@
mIsTaskbarAllAppsOpen = mTaskbarActivityContext.isTaskbarAllAppsOpen();
mIsTransientTaskbar = DisplayController.isTransientTaskbar(context);
+ mTaskbarSlowVelocityYThreshold =
+ res.getDimensionPixelSize(R.dimen.taskbar_slow_velocity_y_threshold);
mBottomScreenEdge = res.getDimensionPixelSize(
R.dimen.taskbar_stashed_screen_edge_hover_deadzone_height);
@@ -103,6 +122,7 @@
mTransitionCallback = mIsTransientTaskbar
? taskbarActivityContext.getTranslationCallbacks()
: null;
+ mGestureState = gestureState;
}
@Override
@@ -111,7 +131,15 @@
}
@Override
+ public boolean allowInterceptByParent() {
+ return super.allowInterceptByParent() && !mHasPassedTaskbarNavThreshold;
+ }
+
+ @Override
public void onMotionEvent(MotionEvent ev) {
+ if (enableScalingRevealHomeAnimation() && mIsTransientTaskbar) {
+ checkVelocityForTaskbarBackground(ev);
+ }
if (mState != STATE_ACTIVE) {
boolean isStashedTaskbarHovered = isMouseEvent(ev)
&& isStashedTaskbarHovered((int) ev.getX(), (int) ev.getY());
@@ -173,7 +201,8 @@
boolean passedTaskbarNavThreshold = dY < 0
&& Math.abs(dY) >= mTaskbarNavThreshold;
- if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) {
+ if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold
+ && !mGestureState.isInExtendedSlopRegion()) {
mHasPassedTaskbarNavThreshold = true;
if (mIsInBubbleBarArea && mIsVerticalGestureOverBubbleBar) {
mTaskbarActivityContext.onSwipeToOpenBubblebar();
@@ -240,6 +269,31 @@
}
}
+ private void checkVelocityForTaskbarBackground(MotionEvent ev) {
+ int actionMasked = ev.getActionMasked();
+ if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ mVelocityTracker.computeCurrentVelocity(1000);
+ if (ev.getAction() == ACTION_MOVE) {
+ mMotionMoveCount++;
+ }
+
+ float velocityYPxPerS = mVelocityTracker.getYVelocity();
+ if (mCanPlayTaskbarBgAlphaAnimation
+ && mMotionMoveCount >= NUM_MOTION_MOVE_THRESHOLD // Arbitrary value
+ && velocityYPxPerS != 0 // Ignore these
+ && velocityYPxPerS >= mTaskbarSlowVelocityYThreshold) {
+ mTaskbarActivityContext.playTaskbarBackgroundAlphaAnimation();
+ mCanPlayTaskbarBgAlphaAnimation = false;
+ }
+ }
+
private void cleanupAfterMotionEvent() {
mTaskbarActivityContext.setAutohideSuspendFlag(
FLAG_AUTOHIDE_SUSPEND_TOUCHING, false);
@@ -250,6 +304,13 @@
mIsInBubbleBarArea = false;
mIsVerticalGestureOverBubbleBar = false;
mIsPassedBubbleBarSlop = false;
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ }
+ mVelocityTracker = null;
+ mCanPlayTaskbarBgAlphaAnimation = true;
+ mMotionMoveCount = 0;
}
private boolean isInBubbleBarArea(float x) {
@@ -299,16 +360,25 @@
dp.heightPx);
if (mBottomEdgeBounds.contains(x, y)) {
- // If hovering stashed taskbar and then hover screen bottom edge, unstash it.
- mTaskbarActivityContext.onSwipeToUnstashTaskbar();
- mIsStashedTaskbarHovered = false;
+ // start a single unstash timeout if hovering bottom edge under the hinted taskbar.
+ if (!sUnstashHandler.hasMessagesOrCallbacks()) {
+ sUnstashHandler.postDelayed(() -> {
+ mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+ mIsStashedTaskbarHovered = false;
+ }, HOVER_TASKBAR_UNSTASH_TIMEOUT);
+ }
} else if (!isStashedTaskbarHovered(x, y)) {
- // If exit hovering stashed taskbar, remove hint.
+ // If exit hovering stashed taskbar, remove hint and clear pending unstash calls.
+ sUnstashHandler.removeCallbacksAndMessages(null);
startStashedTaskbarHover(/* isHovered = */ false);
+ } else {
+ sUnstashHandler.removeCallbacksAndMessages(null);
}
}
private void updateUnhoveredTaskbarState(int x, int y) {
+ sUnstashHandler.removeCallbacksAndMessages(null);
+
DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
mBottomEdgeBounds.set(
0,
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 4198e2d..36ea926 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -177,21 +177,25 @@
&& getResources().getConfiguration().orientation
== ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
- GestureSandboxFragment recreatedFragment =
- showRotationPrompt || mPendingFragment == null
- ? null : mPendingFragment.recreateFragment();
- showFragment(showRotationPrompt
+ GestureSandboxFragment fragment = showRotationPrompt
? new RotationPromptFragment()
- : recreatedFragment == null
- ? mCurrentFragment : recreatedFragment);
+ : mCurrentFragment.canRecreateFragment() || mPendingFragment == null
+ ? mCurrentFragment.recreateFragment()
+ : mPendingFragment.recreateFragment();
+ showFragment(fragment == null ? mCurrentFragment : fragment);
+
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
private void showFragment(@NonNull GestureSandboxFragment fragment) {
- if (mCurrentFragment.recreateFragment() != null) {
+ // Store the current fragment in mPendingFragment so that it can be recreated after the
+ // new fragment is shown.
+ if (mCurrentFragment.canRecreateFragment()) {
mPendingFragment = mCurrentFragment;
+ } else {
+ mPendingFragment = null;
}
mCurrentFragment = fragment;
getSupportFragmentManager().beginTransaction()
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
index 08f2989..03bdd5d 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
@@ -28,6 +28,10 @@
void onDetachedFromWindow() {}
+ boolean canRecreateFragment() {
+ return false;
+ }
+
@Nullable
GestureSandboxFragment recreateFragment() {
return null;
diff --git a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
index dbf141b..8ead3dd 100644
--- a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
@@ -39,6 +39,11 @@
}
@Override
+ boolean canRecreateFragment() {
+ return true;
+ }
+
+ @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
final View root = inflater.inflate(
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index c4a2216..c00f508 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -65,7 +65,7 @@
mSwipeUpTouchTracker =
new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
new NavBarPosition(NavigationMode.NO_BUTTON, displayInfo),
- null /*onInterceptTouch*/, this);
+ this);
mMotionPauseDetector = new MotionPauseDetector(context);
final Resources resources = context.getResources();
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index d5cc447..ad13efb 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -382,7 +382,7 @@
}
@Override
- public void update(RectF rect, float progress, float radius) {
+ public void update(RectF rect, float progress, float radius, int overlayAlpha) {
mFakeIconView.setVisibility(View.VISIBLE);
mFakeIconView.update(rect, progress,
1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */,
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 545a94d..f89888a 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -41,6 +41,7 @@
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -87,7 +88,7 @@
private static final int FEEDBACK_ANIMATION_MS = 133;
private static final int RIPPLE_VISIBLE_MS = 300;
private static final int GESTURE_ANIMATION_DELAY_MS = 1500;
- private static final int ADVANCE_TUTORIAL_TIMEOUT_MS = 2000;
+ private static final int ADVANCE_TUTORIAL_TIMEOUT_MS = 3000;
private static final long GESTURE_ANIMATION_PAUSE_DURATION_MILLIS = 1000;
protected float mExitingAppEndingCornerRadius;
protected float mExitingAppStartingCornerRadius;
@@ -209,8 +210,12 @@
mFeedbackView.removeCallbacks(mFeedbackViewCallback);
}
mFeedbackViewCallback = mTutorialFragment::continueTutorial;
- mFeedbackView.postDelayed(mFeedbackViewCallback,
- ADVANCE_TUTORIAL_TIMEOUT_MS);
+ mFeedbackView.postDelayed(
+ mFeedbackViewCallback,
+ AccessibilityManager.getInstance(mContext)
+ .getRecommendedTimeoutMillis(
+ ADVANCE_TUTORIAL_TIMEOUT_MS,
+ AccessibilityManager.FLAG_CONTENT_TEXT));
}
})
.start();
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index c91ee81..0fafb94 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -114,6 +114,11 @@
return newInstance(tutorialType, isGestureComplete(), mFromTutorialMenu);
}
+ @Override
+ boolean canRecreateFragment() {
+ return true;
+ }
+
@NonNull
abstract TutorialType getDefaultTutorialType();
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index f8d695c..5ac04da 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -148,7 +148,7 @@
@Override
public void onDisplayInfoChanged(Context context, Info info, int flags) {
if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
- mNavMode = info.navigationMode;
+ mNavMode = info.getNavigationMode();
mStatsLogManager.logger().log(mNavMode.launcherEvent);
}
}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index d265918..1d4160d 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,6 +16,10 @@
package com.android.quickstep.logging;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
import static androidx.core.util.Preconditions.checkNotNull;
import static androidx.core.util.Preconditions.checkState;
@@ -26,10 +30,17 @@
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
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__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__SEASCAPE;
import android.content.Context;
import android.text.TextUtils;
@@ -58,11 +69,8 @@
import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
-import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.LogConfig;
import com.android.launcher3.views.ActivityContext;
@@ -113,7 +121,7 @@
new CopyOnWriteArrayList<>();
public StatsLogCompatManager(Context context) {
- mContext = context;
+ super(context);
}
@Override
@@ -230,10 +238,15 @@
private int mInputType = SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__UNKNOWN;
private Optional<Integer> mFeatures = Optional.empty();
private Optional<String> mPackageName = Optional.empty();
+ /**
+ * Indicates the current rotation of the display. Uses {@link android.view.Surface values.}
+ */
+ private final int mDisplayRotation;
StatsCompatLogger(Context context, ActivityContext activityContext) {
mContext = context;
mActivityContext = Optional.ofNullable(activityContext);
+ mDisplayRotation = DisplayController.INSTANCE.get(mContext).getInfo().rotation;
}
@Override
@@ -347,7 +360,6 @@
event.getId() + "";
Log.d(TAG, name);
}
- LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
if (mSlice == null && mSliceItem != null) {
mSlice = LauncherAtom.Slice.newBuilder().setUri(
@@ -369,24 +381,17 @@
return;
}
- if (mItemInfo.container < 0 || appState == null) {
+ if (mItemInfo.container < 0 || !LauncherAppState.INSTANCE.executeIfCreated(app -> {
+ // Item is inside a collection, fetch collection info in a BG thread
+ // and then write to StatsLog.
+ app.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
+ write(event, applyOverwrites(mItemInfo.buildProto(
+ dataModel.collections.get(mItemInfo.container)))));
+ })) {
// Write log on the model thread so that logs do not go out of order
// (for eg: drop comes after drag)
Executors.MODEL_EXECUTOR.execute(
() -> write(event, applyOverwrites(mItemInfo.buildProto())));
- } else {
- // Item is inside the folder, fetch folder info in a BG thread
- // and then write to StatsLog.
- appState.getModel().enqueueModelUpdateTask(
- new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
- FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
- write(event, applyOverwrites(mItemInfo.buildProto(folderInfo)));
- }
- });
}
}
@@ -404,6 +409,20 @@
case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END:
InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL);
break;
+ case LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN:
+ InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_LOCK);
+ break;
+ case LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END:
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_LOCK);
+ break;
+ case LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN:
+ InteractionJankMonitorWrapper.begin(
+ view,
+ Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK);
+ break;
+ case LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END:
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK);
+ break;
default:
break;
}
@@ -500,7 +519,28 @@
getSearchAttributes(atomInfo) /* searchAttributes */,
getAttributes(atomInfo) /* attributes */,
inputType /* input_type */,
- atomInfo.getUserType() /* user_type */);
+ atomInfo.getUserType() /* user_type */,
+ getDisplayRotation() /* display_rotation */,
+ getRecentsOrientationHandler(atomInfo) /* recents_orientation_handler */);
+ }
+
+ private int getDisplayRotation() {
+ return switch (mDisplayRotation) {
+ case ROTATION_90 -> LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90;
+ case ROTATION_180 -> LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_180;
+ case ROTATION_270 -> LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_270;
+ default -> LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_0;
+ };
+ }
+
+ private int getRecentsOrientationHandler(LauncherAtom.ItemInfo itemInfo) {
+ var orientationHandler =
+ itemInfo.getContainerInfo().getTaskSwitcherContainer().getOrientationHandler();
+ return switch (orientationHandler) {
+ case PORTRAIT -> LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT;
+ case LANDSCAPE -> LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__LANDSCAPE;
+ case SEASCAPE -> LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__SEASCAPE;
+ };
}
}
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
deleted file mode 100644
index f345aeb..0000000
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
+++ /dev/null
@@ -1,704 +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.orientation;
-
-import static android.view.Gravity.BOTTOM;
-import static android.view.Gravity.CENTER_VERTICAL;
-import static android.view.Gravity.END;
-import static android.view.Gravity.LEFT;
-import static android.view.Gravity.START;
-import static android.view.Gravity.TOP;
-import static android.view.View.LAYOUT_DIRECTION_RTL;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
-
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ShapeDrawable;
-import android.util.FloatProperty;
-import android.util.Pair;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.views.IconAppChipView;
-
-import java.util.Collections;
-import java.util.List;
-
-public class LandscapePagedViewHandler implements RecentsPagedOrientationHandler {
-
- @Override
- public <T> T getPrimaryValue(T x, T y) {
- return y;
- }
-
- @Override
- public <T> T getSecondaryValue(T x, T y) {
- return x;
- }
-
- @Override
- public int getPrimaryValue(int x, int y) {
- return y;
- }
-
- @Override
- public int getSecondaryValue(int x, int y) {
- return x;
- }
-
- @Override
- public float getPrimaryValue(float x, float y) {
- return y;
- }
-
- @Override
- public float getSecondaryValue(float x, float y) {
- return x;
- }
-
- @Override
- public boolean isLayoutNaturalToLauncher() {
- return false;
- }
-
- @Override
- public void adjustFloatingIconStartVelocity(PointF velocity) {
- float oldX = velocity.x;
- float oldY = velocity.y;
- velocity.set(-oldY, oldX);
- }
-
- @Override
- public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) {
- // We don't need to check the "top" value here because the startRect is in the orientation
- // of the app, not of the fixed portrait launcher.
- if (outStartRect.left > deviceProfile.heightPx) {
- outStartRect.offsetTo(0, outStartRect.top);
- } else if (outStartRect.left < -deviceProfile.heightPx) {
- outStartRect.offsetTo(0, outStartRect.top);
- }
- }
-
- @Override
- public <T> void setPrimary(T target, Int2DAction<T> action, int param) {
- action.call(target, 0, param);
- }
-
- @Override
- public <T> void setPrimary(T target, Float2DAction<T> action, float param) {
- action.call(target, 0, param);
- }
-
- @Override
- public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
- action.call(target, param, 0);
- }
-
- @Override
- public <T> void set(T target, Int2DAction<T> action, int primaryParam,
- int secondaryParam) {
- action.call(target, secondaryParam, primaryParam);
- }
-
- @Override
- public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
- return event.getY(pointerIndex);
- }
-
- @Override
- public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
- return velocityTracker.getYVelocity(pointerId);
- }
-
- @Override
- public int getMeasuredSize(View view) {
- return view.getMeasuredHeight();
- }
-
- @Override
- public int getPrimarySize(View view) {
- return view.getHeight();
- }
-
- @Override
- public float getPrimarySize(RectF rect) {
- return rect.height();
- }
-
- @Override
- public float getStart(RectF rect) {
- return rect.top;
- }
-
- @Override
- public float getEnd(RectF rect) {
- return rect.bottom;
- }
-
- @Override
- public int getClearAllSidePadding(View view, boolean isRtl) {
- return (isRtl ? view.getPaddingBottom() : - view.getPaddingTop()) / 2;
- }
-
- @Override
- public int getSecondaryDimension(View view) {
- return view.getWidth();
- }
-
- @Override
- public FloatProperty<View> getPrimaryViewTranslate() {
- return VIEW_TRANSLATE_Y;
- }
-
- @Override
- public FloatProperty<View> getSecondaryViewTranslate() {
- return VIEW_TRANSLATE_X;
- }
-
- @Override
- public int getPrimaryScroll(View view) {
- return view.getScrollY();
- }
-
- @Override
- public float getPrimaryScale(View view) {
- return view.getScaleY();
- }
-
- @Override
- public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
- event.setMaxScrollY(maxScroll);
- }
-
- @Override
- public boolean getRecentsRtlSetting(Resources resources) {
- return !Utilities.isRtl(resources);
- }
-
- @Override
- public float getDegreesRotated() {
- return 90;
- }
-
- @Override
- public int getRotation() {
- return Surface.ROTATION_90;
- }
-
- @Override
- public void setPrimaryScale(View view, float scale) {
- view.setScaleY(scale);
- }
-
- @Override
- public void setSecondaryScale(View view, float scale) {
- view.setScaleX(scale);
- }
-
- @Override
- public int getChildStart(View view) {
- return view.getTop();
- }
-
- @Override
- public int getCenterForPage(View view, Rect insets) {
- return (view.getPaddingLeft() + view.getMeasuredWidth() + insets.left
- - insets.right - view.getPaddingRight()) / 2;
- }
-
- @Override
- public int getScrollOffsetStart(View view, Rect insets) {
- return insets.top + view.getPaddingTop();
- }
-
- @Override
- public int getScrollOffsetEnd(View view, Rect insets) {
- return view.getHeight() - view.getPaddingBottom() - insets.bottom;
- }
-
- public int getSecondaryTranslationDirectionFactor() {
- return 1;
- }
-
- @Override
- public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
- if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
- return -1;
- } else {
- return 1;
- }
- }
-
- @Override
- public float getTaskMenuX(float x, View thumbnailView,
- DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
- return thumbnailView.getMeasuredWidth() + x - taskInsetMargin;
- }
-
- @Override
- public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin, View taskViewIcon) {
- BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskMenuView.getLayoutParams();
- int taskMenuWidth = lp.width;
- if (stagePosition == STAGE_POSITION_UNDEFINED) {
- return y + taskInsetMargin
- + (thumbnailView.getMeasuredHeight() - taskMenuWidth) / 2f;
- } else {
- return y + taskInsetMargin;
- }
- }
-
- @Override
- public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
- @StagePosition int stagePosition) {
- if (enableOverviewIconMenu()) {
- return thumbnailView.getResources().getDimensionPixelSize(
- R.dimen.task_thumbnail_icon_menu_expanded_width);
- }
- if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_UNDEFINED) {
- return thumbnailView.getMeasuredWidth();
- } else {
- return thumbnailView.getMeasuredHeight();
- }
- }
-
- @Override
- public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
- float taskMenuX, float taskMenuY) {
- return (int) (taskMenuX - taskInsetMargin);
- }
-
- @Override
- public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
- LinearLayout taskMenuLayout, int dividerSpacing,
- ShapeDrawable dividerDrawable) {
- taskMenuLayout.setOrientation(LinearLayout.VERTICAL);
- dividerDrawable.setIntrinsicHeight(dividerSpacing);
- taskMenuLayout.setDividerDrawable(dividerDrawable);
- }
-
- @Override
- public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
- LinearLayout viewGroup, DeviceProfile deviceProfile) {
- // Phone fake landscape
- viewGroup.setOrientation(LinearLayout.HORIZONTAL);
- lp.width = MATCH_PARENT;
- lp.height = WRAP_CONTENT;
- }
-
- @Override
- public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
- int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
- View[] thumbnailViews, int desiredTaskId, View banner) {
- boolean isRtl = banner.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- float translationX = 0;
- float translationY = 0;
- FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
- banner.setPivotX(0);
- banner.setPivotY(0);
- banner.setRotation(getDegreesRotated());
- translationX = banner.getHeight();
- FrameLayout.LayoutParams snapshotParams =
- (FrameLayout.LayoutParams) thumbnailViews[0]
- .getLayoutParams();
- bannerParams.gravity = TOP | (isRtl ? END : START);
- if (splitBounds == null) {
- // Single, fullscreen case
- bannerParams.width = taskViewHeight - snapshotParams.topMargin;
- return new Pair<>(translationX, Integer.valueOf(snapshotParams.topMargin).floatValue());
- }
-
- // Set correct width
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- bannerParams.width = thumbnailViews[0].getMeasuredHeight();
- } else {
- bannerParams.width = thumbnailViews[1].getMeasuredHeight();
- }
-
- // Set translations
- if (desiredTaskId == splitBounds.rightBottomTaskId) {
- float topLeftTaskPlusDividerPercent = splitBounds.appsStackedVertically
- ? (splitBounds.topTaskPercent + splitBounds.dividerHeightPercent)
- : (splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent);
- translationY = snapshotParams.topMargin
- + ((taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent);
- }
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- translationY = snapshotParams.topMargin;
- }
- return new Pair<>(translationX, translationY);
- }
-
- /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
-
- @Override
- public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
- return HORIZONTAL;
- }
-
- @Override
- public int getUpDirection(boolean isRtl) {
- return isRtl ? SingleAxisSwipeDetector.DIRECTION_NEGATIVE
- : SingleAxisSwipeDetector.DIRECTION_POSITIVE;
- }
-
- @Override
- public boolean isGoingUp(float displacement, boolean isRtl) {
- return isRtl ? displacement < 0 : displacement > 0;
- }
-
- @Override
- public int getTaskDragDisplacementFactor(boolean isRtl) {
- return isRtl ? 1 : -1;
- }
-
- /* -------------------- */
-
- @Override
- public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
- boolean layoutChild) {
- final int childHeight = child.getMeasuredHeight();
- final int childBottom = childStart + childHeight;
- final int childWidth = child.getMeasuredWidth();
- final int childLeft = pageCenter - childWidth/ 2;
- if (layoutChild) {
- child.layout(childLeft, childStart, childLeft + childWidth, childBottom);
- }
- return new ChildBounds(childHeight, childWidth, childBottom, childLeft);
- }
-
- @SuppressWarnings("SuspiciousNameCombination")
- @Override
- public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
- return rect.left;
- }
-
- @Override
- public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
- // Add "left" side of phone which is actually the top
- return Collections.singletonList(new SplitPositionOption(
- R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
- STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
- }
-
- @Override
- public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
- DeviceProfile dp, @StagePosition int stagePosition, Rect out) {
- // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
- // which is the same bounds as 0 rotation.
- int width = dp.widthPx;
- int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp);
- out.set(0, 0, width, placeholderHeight + insetSizeAdjustment);
- out.inset(placeholderInset, 0);
-
- // Adjust the top to account for content off screen. This will help to animate the view in
- // with rounded corners.
- int screenWidth = dp.widthPx;
- int screenHeight = dp.heightPx;
- int totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset)
- / screenWidth);
- out.top -= (totalHeight - placeholderHeight);
- }
-
- @Override
- public void updateSplitIconParams(View out, float onScreenRectCenterX,
- float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
- int drawableWidth, int drawableHeight, DeviceProfile dp,
- @StagePosition int stagePosition) {
- float insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f;
- out.setX(onScreenRectCenterX / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY
- - 1.0f * drawableHeight / 2);
- }
-
- /**
- * The split placeholder comes with a default inset to buffer the icon from the top of the
- * screen. But if the device already has a large inset (from cutouts etc), use that instead.
- */
- private int getPlaceholderSizeAdjustment(DeviceProfile dp) {
- return Math.max(dp.getInsets().top - dp.splitPlaceholderInset, 0);
- }
-
- @Override
- public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
- int splitInstructionsWidth) {
- out.setPivotX(0);
- out.setPivotY(splitInstructionsHeight);
- out.setRotation(getDegreesRotated());
- int distanceToEdge = out.getResources().getDimensionPixelSize(
- R.dimen.split_instructions_bottom_margin_phone_landscape);
- // Adjust for any insets on the left edge
- int insetCorrectionX = dp.getInsets().left;
- // Center the view in case of unbalanced insets on top or bottom of screen
- int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
- out.setTranslationX(distanceToEdge - insetCorrectionX);
- out.setTranslationY(((-splitInstructionsHeight - splitInstructionsWidth) / 2f)
- + insetCorrectionY);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
- // Setting gravity to LEFT instead of the lint-recommended START because we always want this
- // view to be screen-left when phone is in landscape, regardless of the RtL setting.
- lp.gravity = LEFT | CENTER_VERTICAL;
- out.setLayoutParams(lp);
- }
-
- @Override
- public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- @StagePosition int stagePosition, Rect out1, Rect out2) {
- // In fake land/seascape, the window bounds are always top and bottom half
- int screenHeight = dp.heightPx;
- int screenWidth = dp.widthPx;
- out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
- out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
- }
-
- @Override
- public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
- SplitBounds splitInfo, int desiredStagePosition) {
- float topLeftTaskPercent = splitInfo.appsStackedVertically
- ? splitInfo.topTaskPercent
- : splitInfo.leftTaskPercent;
- float dividerBarPercent = splitInfo.appsStackedVertically
- ? splitInfo.dividerHeightPercent
- : splitInfo.dividerWidthPercent;
-
- if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
- outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent);
- } else {
- outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
- }
- }
-
- @Override
- public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp,
- boolean isRtl) {
- FrameLayout.LayoutParams primaryParams =
- (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
- FrameLayout.LayoutParams secondaryParams =
- (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
-
- // Swap the margins that are set in TaskView#setRecentsOrientedState()
- secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx;
- primaryParams.topMargin = 0;
-
- // Measure and layout the thumbnails bottom up, since the primary is on the visual left
- // (portrait bottom) and secondary is on the right (portrait top)
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.dividerHeightPercent
- : splitBoundsConfig.dividerWidthPercent));
-
- Pair<Point, Point> taskViewSizes =
- getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
-
- int translationY = taskViewSizes.first.y + spaceAboveSnapshot + dividerBar;
- primarySnapshot.setTranslationY(spaceAboveSnapshot);
- secondarySnapshot.setTranslationY(translationY - spaceAboveSnapshot);
-
- primarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY)
- );
- secondarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y, View.MeasureSpec.EXACTLY)
- );
- }
-
- @Override
- public Pair<Point, Point> getGroupedTaskViewSizes(
- DeviceProfile dp,
- SplitBounds splitBoundsConfig,
- int parentWidth,
- int parentHeight) {
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.dividerHeightPercent
- : splitBoundsConfig.dividerWidthPercent));
- float taskPercent = splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.topTaskPercent
- : splitBoundsConfig.leftTaskPercent;
-
- Point firstTaskViewSize = new Point(
- parentWidth,
- (int) (totalThumbnailHeight * taskPercent)
- );
- Point secondTaskViewSize = new Point(
- parentWidth,
- totalThumbnailHeight - firstTaskViewSize.y - dividerBar
- );
-
- return new Pair<>(firstTaskViewSize, secondTaskViewSize);
- }
-
- @Override
- public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin,
- int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
- iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
- iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2;
- iconParams.leftMargin = 0;
- iconParams.topMargin = thumbnailTopMargin / 2;
- iconParams.bottomMargin = 0;
- }
-
- @Override
- public void setIconAppChipChildrenParams(FrameLayout.LayoutParams iconParams,
- int chipChildMarginStart) {
- iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
- iconParams.setMarginStart(chipChildMarginStart);
- iconParams.topMargin = 0;
- }
-
- @Override
- public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
- FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
- boolean isRtl = iconAppChipView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- iconMenuParams.gravity = (isRtl ? START : END) | (isRtl ? BOTTOM : TOP);
- iconMenuParams.setMarginStart(isRtl ? iconMenuMargin : 0);
- iconMenuParams.topMargin = iconMenuMargin;
- iconMenuParams.bottomMargin = isRtl ? iconMenuMargin : 0;
- iconMenuParams.setMarginEnd(iconMenuMargin);
-
- iconAppChipView.setPivotX(isRtl ? iconMenuParams.width - (iconMenuParams.height / 2f)
- : iconMenuParams.width / 2f);
- iconAppChipView.setPivotY(
- isRtl ? (iconMenuParams.height / 2f) : iconMenuParams.width / 2f);
- iconAppChipView.setSplitTranslationY(0);
- iconAppChipView.setRotation(getDegreesRotated());
- }
-
- @Override
- public void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
- int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig) {
- FrameLayout.LayoutParams primaryIconParams =
- (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
- FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
- ? (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams()
- : new FrameLayout.LayoutParams(primaryIconParams);
-
- // We calculate the "midpoint" of the thumbnail area, and place the icons there.
- // This is the place where the thumbnail area splits by default, in a near-50/50 split.
- // It is usually not exactly 50/50, due to insets/screen cutouts.
- int fullscreenInsetThickness = deviceProfile.getInsets().top
- - deviceProfile.getInsets().bottom;
- int fullscreenMidpointFromBottom = ((deviceProfile.heightPx - fullscreenInsetThickness)
- / 2);
- float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
- float insetPct = (float) fullscreenInsetThickness / deviceProfile.heightPx;
- int spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx;
- int overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots;
- int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness * midpointFromBottomPct);
- int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
-
- if (enableOverviewIconMenu()) {
- primaryIconParams.gravity = isRtl ? BOTTOM | START : TOP | END;
- secondaryIconParams.gravity = isRtl ? BOTTOM | START : TOP | END;
- } else {
- primaryIconParams.gravity = BOTTOM | (isRtl ? START : END);
- secondaryIconParams.gravity = BOTTOM | (isRtl ? START : END);
- }
- primaryIconView.setTranslationX(0);
- secondaryIconView.setTranslationX(0);
- if (enableOverviewIconMenu()) {
- IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
- IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
- if (primaryIconView.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
- secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight);
- primaryAppChipView.setSplitTranslationY(0);
- } else {
- int secondarySnapshotHeight = groupedTaskViewHeight - primarySnapshotHeight;
- primaryAppChipView.setSplitTranslationY(secondarySnapshotHeight);
- }
- } else if (splitConfig.initiatedFromSeascape) {
- // if the split was initiated from seascape,
- // the task on the right (secondary) is slightly larger
- primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
- secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
- + taskIconHeight);
- } else {
- // if not,
- // the task on the left (primary) is slightly larger
- primaryIconView.setTranslationY(-bottomToMidpointOffset);
- secondaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
- }
-
- primaryIconView.setLayoutParams(primaryIconParams);
- secondaryIconView.setLayoutParams(secondaryIconParams);
- }
-
- @Override
- public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
- throw new IllegalStateException("Default position not available in fake landscape");
- }
-
- @Override
- public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
- FloatProperty secondary, DeviceProfile deviceProfile) {
- return new Pair<>(primary, secondary);
- }
-
- @Override
- public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
- @StagePosition int stagePosition, DeviceProfile dp) {
- float currentTranslationY = floatingTask.getTranslationY();
- return currentTranslationY - onScreenRect.height();
- }
-
- @Override
- public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
- DeviceProfile dp) {
- floatingTask.setTranslationY(translation);
- }
-
- @Override
- public Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
- return floatingTask.getTranslationY();
- }
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
new file mode 100644
index 0000000..ec04cb7
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -0,0 +1,708 @@
+/*
+ * 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.orientation
+
+import android.annotation.SuppressLint
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.ShapeDrawable
+import android.util.FloatProperty
+import android.util.Pair
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.Surface
+import android.view.VelocityTracker
+import android.view.View
+import android.view.View.MeasureSpec
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.annotation.VisibleForTesting
+import androidx.core.util.component1
+import androidx.core.util.component2
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer
+import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds
+import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
+import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.launcher3.views.BaseDragLayer
+import com.android.quickstep.views.IconAppChipView
+import kotlin.math.max
+
+open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
+ override fun <T> getPrimaryValue(x: T, y: T): T = y
+
+ override fun <T> getSecondaryValue(x: T, y: T): T = x
+
+ override fun getPrimaryValue(x: Int, y: Int): Int = y
+
+ override fun getSecondaryValue(x: Int, y: Int): Int = x
+
+ override fun getPrimaryValue(x: Float, y: Float): Float = y
+
+ override fun getSecondaryValue(x: Float, y: Float): Float = x
+
+ override val isLayoutNaturalToLauncher: Boolean = false
+
+ override fun adjustFloatingIconStartVelocity(velocity: PointF) {
+ val oldX = velocity.x
+ val oldY = velocity.y
+ velocity.set(-oldY, oldX)
+ }
+
+ override fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile) {
+ // We don't need to check the "top" value here because the startRect is in the orientation
+ // of the app, not of the fixed portrait launcher.
+ if (outStartRect.left > deviceProfile.heightPx) {
+ outStartRect.offsetTo(0f, outStartRect.top)
+ } else if (outStartRect.left < -deviceProfile.heightPx) {
+ outStartRect.offsetTo(0f, outStartRect.top)
+ }
+ }
+
+ override fun <T> setPrimary(target: T, action: Int2DAction<T>, param: Int) =
+ action.call(target, 0, param)
+
+ override fun <T> setPrimary(target: T, action: Float2DAction<T>, param: Float) =
+ action.call(target, 0f, param)
+
+ override fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float) =
+ action.call(target, param, 0f)
+
+ override fun <T> set(
+ target: T,
+ action: Int2DAction<T>,
+ primaryParam: Int,
+ secondaryParam: Int
+ ) = action.call(target, secondaryParam, primaryParam)
+
+ override fun getPrimaryDirection(event: MotionEvent, pointerIndex: Int): Float =
+ event.getY(pointerIndex)
+
+ override fun getPrimaryVelocity(velocityTracker: VelocityTracker, pointerId: Int): Float =
+ velocityTracker.getYVelocity(pointerId)
+
+ override fun getMeasuredSize(view: View): Int = view.measuredHeight
+
+ override fun getPrimarySize(view: View): Int = view.height
+
+ override fun getPrimarySize(rect: RectF): Float = rect.height()
+
+ override fun getStart(rect: RectF): Float = rect.top
+
+ override fun getEnd(rect: RectF): Float = rect.bottom
+
+ override fun rotateInsets(insets: Rect, outInsets: Rect) {
+ outInsets.set(insets.bottom, insets.left, insets.top, insets.right)
+ }
+
+ override fun getClearAllSidePadding(view: View, isRtl: Boolean): Int =
+ if (isRtl) view.paddingBottom / 2 else -view.paddingTop / 2
+
+ override fun getSecondaryDimension(view: View): Int = view.width
+
+ override val primaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_Y
+
+ override val secondaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_X
+
+ override fun getPrimaryScroll(view: View): Int = view.scrollY
+
+ override fun getPrimaryScale(view: View): Float = view.scaleY
+
+ override fun setMaxScroll(event: AccessibilityEvent, maxScroll: Int) {
+ event.maxScrollY = maxScroll
+ }
+
+ override fun getRecentsRtlSetting(resources: Resources): Boolean = !Utilities.isRtl(resources)
+
+ override val degreesRotated: Float = 90f
+
+ override val rotation: Int = Surface.ROTATION_90
+
+ override fun setPrimaryScale(view: View, scale: Float) {
+ view.scaleY = scale
+ }
+
+ override fun setSecondaryScale(view: View, scale: Float) {
+ view.scaleX = scale
+ }
+
+ override fun getChildStart(view: View): Int = view.top
+
+ override fun getCenterForPage(view: View, insets: Rect): Int =
+ (view.paddingLeft + view.measuredWidth + insets.left - insets.right - view.paddingRight) / 2
+
+ override fun getScrollOffsetStart(view: View, insets: Rect): Int = insets.top + view.paddingTop
+
+ override fun getScrollOffsetEnd(view: View, insets: Rect): Int =
+ view.height - view.paddingBottom - insets.bottom
+
+ override val secondaryTranslationDirectionFactor: Int = 1
+
+ override fun getSplitTranslationDirectionFactor(
+ stagePosition: Int,
+ deviceProfile: DeviceProfile
+ ): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
+
+ override fun getTaskMenuX(
+ x: Float,
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float = thumbnailView.measuredWidth + x - taskInsetMargin
+
+ override fun getTaskMenuY(
+ y: Float,
+ thumbnailView: View,
+ stagePosition: Int,
+ taskMenuView: View,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float {
+ val layoutParams = taskMenuView.layoutParams as BaseDragLayer.LayoutParams
+ var taskMenuY = y + taskInsetMargin
+
+ if (stagePosition == STAGE_POSITION_UNDEFINED) {
+ taskMenuY += (thumbnailView.measuredHeight - layoutParams.width) / 2f
+ }
+
+ return taskMenuY
+ }
+
+ override fun getTaskMenuWidth(
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ @StagePosition stagePosition: Int
+ ): Int =
+ when {
+ Flags.enableOverviewIconMenu() ->
+ thumbnailView.resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_expanded_width
+ )
+ stagePosition == STAGE_POSITION_UNDEFINED -> thumbnailView.measuredWidth
+ else -> thumbnailView.measuredHeight
+ }
+
+ override fun getTaskMenuHeight(
+ taskInsetMargin: Float,
+ deviceProfile: DeviceProfile,
+ taskMenuX: Float,
+ taskMenuY: Float
+ ): Int = (taskMenuX - taskInsetMargin).toInt()
+
+ override fun setTaskOptionsMenuLayoutOrientation(
+ deviceProfile: DeviceProfile,
+ taskMenuLayout: LinearLayout,
+ dividerSpacing: Int,
+ dividerDrawable: ShapeDrawable
+ ) {
+ taskMenuLayout.orientation = LinearLayout.VERTICAL
+ dividerDrawable.intrinsicHeight = dividerSpacing
+ taskMenuLayout.dividerDrawable = dividerDrawable
+ }
+
+ override fun setLayoutParamsForTaskMenuOptionItem(
+ lp: LinearLayout.LayoutParams,
+ viewGroup: LinearLayout,
+ deviceProfile: DeviceProfile
+ ) {
+ // Phone fake landscape
+ viewGroup.orientation = LinearLayout.HORIZONTAL
+ lp.width = ViewGroup.LayoutParams.MATCH_PARENT
+ lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ }
+
+ override fun getDwbLayoutTranslations(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ splitBounds: SplitBounds?,
+ deviceProfile: DeviceProfile,
+ thumbnailViews: Array<View>,
+ desiredTaskId: Int,
+ banner: View
+ ): Pair<Float, Float> {
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+ val isRtl = banner.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ val translationX = banner.height.toFloat()
+
+ val bannerParams = banner.layoutParams as FrameLayout.LayoutParams
+ bannerParams.gravity = Gravity.TOP or if (isRtl) Gravity.END else Gravity.START
+ banner.pivotX = 0f
+ banner.pivotY = 0f
+ banner.rotation = degreesRotated
+
+ if (splitBounds == null) {
+ // Single, fullscreen case
+ bannerParams.width = taskViewHeight - snapshotParams.topMargin
+ return Pair(translationX, snapshotParams.topMargin.toFloat())
+ }
+
+ // Set correct width and translations
+ val translationY: Float
+ if (desiredTaskId == splitBounds.leftTopTaskId) {
+ bannerParams.width = thumbnailViews[0].measuredHeight
+ translationY = snapshotParams.topMargin.toFloat()
+ } else {
+ bannerParams.width = thumbnailViews[1].measuredHeight
+ val topLeftTaskPlusDividerPercent =
+ if (splitBounds.appsStackedVertically) {
+ splitBounds.topTaskPercent + splitBounds.dividerHeightPercent
+ } else {
+ splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent
+ }
+ translationY =
+ snapshotParams.topMargin +
+ (taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent
+ }
+
+ return Pair(translationX, translationY)
+ }
+
+ /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+ override val upDownSwipeDirection: SingleAxisSwipeDetector.Direction =
+ SingleAxisSwipeDetector.HORIZONTAL
+
+ override fun getUpDirection(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
+
+ override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) 1 else -1
+ /* -------------------- */
+
+ override fun getChildBounds(
+ child: View,
+ childStart: Int,
+ pageCenter: Int,
+ layoutChild: Boolean
+ ): ChildBounds {
+ val childHeight = child.measuredHeight
+ val childWidth = child.measuredWidth
+ val childBottom = childStart + childHeight
+ val childLeft = pageCenter - childWidth / 2
+ if (layoutChild) {
+ child.layout(childLeft, childStart, childLeft + childWidth, childBottom)
+ }
+ return ChildBounds(childHeight, childWidth, childBottom, childLeft)
+ }
+
+ override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int = rect.left
+
+ override fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption> =
+ // Add "left" side of phone which is actually the top
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_horizontal,
+ R.string.recent_task_option_split_screen,
+ STAGE_POSITION_TOP_OR_LEFT,
+ STAGE_TYPE_MAIN
+ )
+ )
+
+ override fun getInitialSplitPlaceholderBounds(
+ placeholderHeight: Int,
+ placeholderInset: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out: Rect
+ ) {
+ // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
+ // which is the same bounds as 0 rotation.
+ val width = dp.widthPx
+ val insetSizeAdjustment = getPlaceholderSizeAdjustment(dp)
+ out.set(0, 0, width, placeholderHeight + insetSizeAdjustment)
+ out.inset(placeholderInset, 0)
+
+ // Adjust the top to account for content off screen. This will help to animate the view in
+ // with rounded corners.
+ val screenWidth = dp.widthPx
+ val screenHeight = dp.heightPx
+ val totalHeight =
+ (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset) / screenWidth).toInt()
+ out.top -= totalHeight - placeholderHeight
+ }
+
+ override fun updateSplitIconParams(
+ out: View,
+ onScreenRectCenterX: Float,
+ onScreenRectCenterY: Float,
+ fullscreenScaleX: Float,
+ fullscreenScaleY: Float,
+ drawableWidth: Int,
+ drawableHeight: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int
+ ) {
+ val insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f
+ out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
+ out.y =
+ ((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY - 1.0f * drawableHeight / 2)
+ }
+
+ /**
+ * The split placeholder comes with a default inset to buffer the icon from the top of the
+ * screen. But if the device already has a large inset (from cutouts etc), use that instead.
+ */
+ private fun getPlaceholderSizeAdjustment(dp: DeviceProfile?): Int =
+ max((dp!!.insets.top - dp.splitPlaceholderInset).toDouble(), 0.0).toInt()
+
+ override fun setSplitInstructionsParams(
+ out: View,
+ dp: DeviceProfile,
+ splitInstructionsHeight: Int,
+ splitInstructionsWidth: Int
+ ) {
+ out.pivotX = 0f
+ out.pivotY = splitInstructionsHeight.toFloat()
+ out.rotation = degreesRotated
+ val distanceToEdge =
+ out.resources.getDimensionPixelSize(
+ R.dimen.split_instructions_bottom_margin_phone_landscape
+ )
+ // Adjust for any insets on the left edge
+ val insetCorrectionX = dp.insets.left
+ // Center the view in case of unbalanced insets on top or bottom of screen
+ val insetCorrectionY = (dp.insets.bottom - dp.insets.top) / 2
+ out.translationX = (distanceToEdge - insetCorrectionX).toFloat()
+ out.translationY =
+ (-splitInstructionsHeight - splitInstructionsWidth) / 2f + insetCorrectionY
+ // Setting gravity to LEFT instead of the lint-recommended START because we always want this
+ // view to be screen-left when phone is in landscape, regardless of the RtL setting.
+ val lp = out.layoutParams as FrameLayout.LayoutParams
+ lp.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
+ out.layoutParams = lp
+ }
+
+ override fun getFinalSplitPlaceholderBounds(
+ splitDividerSize: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out1: Rect,
+ out2: Rect
+ ) {
+ // In fake land/seascape, the window bounds are always top and bottom half
+ val screenHeight = dp.heightPx
+ val screenWidth = dp.widthPx
+ out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize)
+ out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight)
+ }
+
+ override fun setSplitTaskSwipeRect(
+ dp: DeviceProfile,
+ outRect: Rect,
+ splitInfo: SplitBounds,
+ desiredStagePosition: Int
+ ) {
+ val topLeftTaskPercent: Float
+ val dividerBarPercent: Float
+ if (splitInfo.appsStackedVertically) {
+ topLeftTaskPercent = splitInfo.topTaskPercent
+ dividerBarPercent = splitInfo.dividerHeightPercent
+ } else {
+ topLeftTaskPercent = splitInfo.leftTaskPercent
+ dividerBarPercent = splitInfo.dividerWidthPercent
+ }
+
+ if (desiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ outRect.bottom = outRect.top + (outRect.height() * topLeftTaskPercent).toInt()
+ } else {
+ outRect.top += (outRect.height() * (topLeftTaskPercent + dividerBarPercent)).toInt()
+ }
+ }
+
+ override fun measureGroupedTaskViewThumbnailBounds(
+ primarySnapshot: View,
+ secondarySnapshot: View,
+ parentWidth: Int,
+ parentHeight: Int,
+ splitBoundsConfig: SplitBounds,
+ dp: DeviceProfile,
+ isRtl: Boolean
+ ) {
+ val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
+ val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
+
+ // Swap the margins that are set in TaskView#setRecentsOrientedState()
+ secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx
+ primaryParams.topMargin = 0
+
+ // Measure and layout the thumbnails bottom up, since the primary is on the visual left
+ // (portrait bottom) and secondary is on the right (portrait top)
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
+
+ val (taskViewFirst, taskViewSecond) =
+ getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
+
+ primarySnapshot.translationY = spaceAboveSnapshot.toFloat()
+ primarySnapshot.measure(
+ MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+ )
+ val translationY = taskViewFirst.y + spaceAboveSnapshot + dividerBar
+ secondarySnapshot.translationY = (translationY - spaceAboveSnapshot).toFloat()
+ secondarySnapshot.measure(
+ MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+ )
+ }
+
+ override fun getGroupedTaskViewSizes(
+ dp: DeviceProfile,
+ splitBoundsConfig: SplitBounds,
+ parentWidth: Int,
+ parentHeight: Int
+ ): Pair<Point, Point> {
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
+
+ val taskPercent =
+ if (splitBoundsConfig.appsStackedVertically) {
+ splitBoundsConfig.topTaskPercent
+ } else {
+ splitBoundsConfig.leftTaskPercent
+ }
+ val firstTaskViewSize = Point(parentWidth, (totalThumbnailHeight * taskPercent).toInt())
+ val secondTaskViewSize =
+ Point(parentWidth, totalThumbnailHeight - firstTaskViewSize.y - dividerBar)
+ return Pair(firstTaskViewSize, secondTaskViewSize)
+ }
+
+ override fun setTaskIconParams(
+ iconParams: FrameLayout.LayoutParams,
+ taskIconMargin: Int,
+ taskIconHeight: Int,
+ thumbnailTopMargin: Int,
+ isRtl: Boolean
+ ) {
+ iconParams.gravity =
+ if (isRtl) {
+ Gravity.START or Gravity.CENTER_VERTICAL
+ } else {
+ Gravity.END or Gravity.CENTER_VERTICAL
+ }
+ iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2
+ iconParams.leftMargin = 0
+ iconParams.topMargin = thumbnailTopMargin / 2
+ iconParams.bottomMargin = 0
+ }
+
+ override fun setIconAppChipChildrenParams(
+ iconParams: FrameLayout.LayoutParams,
+ chipChildMarginStart: Int
+ ) {
+ iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+ iconParams.marginStart = chipChildMarginStart
+ iconParams.topMargin = 0
+ }
+
+ override fun setIconAppChipMenuParams(
+ iconAppChipView: IconAppChipView,
+ iconMenuParams: FrameLayout.LayoutParams,
+ iconMenuMargin: Int,
+ thumbnailTopMargin: Int
+ ) {
+ val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
+
+ if (isRtl) {
+ iconMenuParams.gravity = Gravity.START or Gravity.BOTTOM
+ iconMenuParams.marginStart = iconMenuMargin
+ iconMenuParams.bottomMargin = iconMenuMargin
+ iconAppChipView.pivotX = iconMenuParams.width - iconMenuParams.height / 2f
+ iconAppChipView.pivotY = iconMenuParams.height / 2f
+ } else {
+ iconMenuParams.gravity = Gravity.END or Gravity.TOP
+ iconMenuParams.marginStart = 0
+ iconMenuParams.bottomMargin = 0
+ iconAppChipView.pivotX = iconMenuParams.width / 2f
+ iconAppChipView.pivotY = iconMenuParams.width / 2f
+ }
+
+ iconMenuParams.topMargin = iconMenuMargin
+ iconMenuParams.marginEnd = iconMenuMargin
+ iconAppChipView.setSplitTranslationY(0f)
+ iconAppChipView.setRotation(degreesRotated)
+ }
+
+ override fun setSplitIconParams(
+ primaryIconView: View,
+ secondaryIconView: View,
+ taskIconHeight: Int,
+ primarySnapshotWidth: Int,
+ primarySnapshotHeight: Int,
+ groupedTaskViewHeight: Int,
+ groupedTaskViewWidth: Int,
+ isRtl: Boolean,
+ deviceProfile: DeviceProfile,
+ splitConfig: SplitBounds
+ ) {
+ val spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = groupedTaskViewHeight - spaceAboveSnapshot
+ val dividerBar: Int = getDividerBarSize(totalThumbnailHeight, splitConfig)
+
+ val (topLeftY, bottomRightY) =
+ getSplitIconsPosition(
+ taskIconHeight,
+ primarySnapshotHeight,
+ totalThumbnailHeight,
+ isRtl,
+ deviceProfile.overviewTaskMarginPx,
+ dividerBar
+ )
+
+ updateSplitIconsPosition(primaryIconView, topLeftY, isRtl)
+ updateSplitIconsPosition(secondaryIconView, bottomRightY, isRtl)
+ }
+
+ override fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int {
+ throw IllegalStateException("Default position not available in fake landscape")
+ }
+
+ override fun <T> getSplitSelectTaskOffset(
+ primary: FloatProperty<T>,
+ secondary: FloatProperty<T>,
+ deviceProfile: DeviceProfile
+ ): Pair<FloatProperty<T>, FloatProperty<T>> = Pair(primary, secondary)
+
+ override fun getFloatingTaskOffscreenTranslationTarget(
+ floatingTask: View,
+ onScreenRect: RectF,
+ @StagePosition stagePosition: Int,
+ dp: DeviceProfile
+ ): Float = floatingTask.translationY - onScreenRect.height()
+
+ override fun setFloatingTaskPrimaryTranslation(
+ floatingTask: View,
+ translation: Float,
+ dp: DeviceProfile
+ ) {
+ floatingTask.translationY = translation
+ }
+
+ override fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float =
+ floatingTask.translationY
+
+ override fun getHandlerTypeForLogging(): TaskSwitcherContainer.OrientationHandler =
+ TaskSwitcherContainer.OrientationHandler.LANDSCAPE
+
+ /**
+ * Retrieves split icons position
+ *
+ * @param taskIconHeight The height of the task icon.
+ * @param primarySnapshotHeight The height for the primary snapshot (i.e., top-left snapshot).
+ * @param totalThumbnailHeight The total height for the group task view.
+ * @param isRtl Whether the layout direction is RTL (or false for LTR).
+ * @param overviewTaskMarginPx The space under the focused task icon provided by Device Profile.
+ * @param dividerSize The size of the divider for the group task view.
+ * @return The top-left and right-bottom positions for the icon views.
+ */
+ @VisibleForTesting
+ open fun getSplitIconsPosition(
+ taskIconHeight: Int,
+ primarySnapshotHeight: Int,
+ totalThumbnailHeight: Int,
+ isRtl: Boolean,
+ overviewTaskMarginPx: Int,
+ dividerSize: Int,
+ ): SplitIconPositions {
+ return if (Flags.enableOverviewIconMenu()) {
+ if (isRtl) {
+ SplitIconPositions(0, -(totalThumbnailHeight - primarySnapshotHeight))
+ } else {
+ SplitIconPositions(0, primarySnapshotHeight + dividerSize)
+ }
+ } else {
+ val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
+ SplitIconPositions(
+ topLeftY = topLeftY,
+ bottomRightY = topLeftY + dividerSize + taskIconHeight
+ )
+ }
+ }
+
+ /**
+ * Updates icon view gravity and translation for split tasks
+ *
+ * @param iconView View to be updated
+ * @param translationY the translationY that should be applied
+ * @param isRtl Whether the layout direction is RTL (or false for LTR).
+ */
+ @SuppressLint("RtlHardcoded")
+ @VisibleForTesting
+ open fun updateSplitIconsPosition(iconView: View, translationY: Int, isRtl: Boolean) {
+ val layoutParams = iconView.layoutParams as FrameLayout.LayoutParams
+
+ if (Flags.enableOverviewIconMenu()) {
+ val appChipView = iconView as IconAppChipView
+ layoutParams.gravity =
+ if (isRtl) Gravity.BOTTOM or Gravity.START else Gravity.TOP or Gravity.END
+ appChipView.layoutParams = layoutParams
+ appChipView.setSplitTranslationX(0f)
+ appChipView.setSplitTranslationY(translationY.toFloat())
+ } else {
+ layoutParams.gravity = Gravity.TOP or Gravity.RIGHT
+ layoutParams.topMargin = translationY
+ iconView.translationX = 0f
+ iconView.translationY = 0f
+ iconView.layoutParams = layoutParams
+ }
+ }
+
+ /**
+ * It calculates the divider's size in the group task view.
+ *
+ * @param totalThumbnailHeight The total height for the group task view
+ * @param splitConfig Contains information about sizes and proportions for split task.
+ * @return The divider size for the group task view.
+ */
+ protected fun getDividerBarSize(totalThumbnailHeight: Int, splitConfig: SplitBounds): Int {
+ return Math.round(
+ totalThumbnailHeight *
+ if (splitConfig.appsStackedVertically) splitConfig.dividerHeightPercent
+ else splitConfig.dividerWidthPercent
+ )
+ }
+
+ /**
+ * Data structure to keep the y position to be used for the split task icon views translation.
+ *
+ * @param topLeftY The y-axis position for the task view position on the Top or Left side.
+ * @param bottomRightY The y-axis position for the task view position on the Bottom or Right
+ * side.
+ */
+ data class SplitIconPositions(val topLeftY: Int, val bottomRightY: Int)
+}
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index 5cd9776..eeacee1 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -46,9 +46,12 @@
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.touch.DefaultPagedViewHandler;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.SplitConfigurationOptions;
@@ -75,6 +78,7 @@
public <T> T getSecondaryValue(T x, T y) {
return y;
}
+
@Override
public boolean isLayoutNaturalToLauncher() {
return true;
@@ -126,6 +130,11 @@
}
@Override
+ public void rotateInsets(@NonNull Rect insets, @NonNull Rect outInsets) {
+ outInsets.set(insets);
+ }
+
+ @Override
public int getClearAllSidePadding(View view, boolean isRtl) {
return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
}
@@ -212,7 +221,8 @@
@Override
public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
float taskMenuX, float taskMenuY) {
- return (int) (deviceProfile.availableHeightPx - taskInsetMargin - taskMenuY);
+ return (int) (deviceProfile.heightPx - deviceProfile.getInsets().top - taskMenuY
+ - deviceProfile.getOverviewActionsClaimedSpaceBelow());
}
@Override
@@ -249,7 +259,8 @@
return new Pair<>(translationX, translationY);
}
- bannerParams.gravity = BOTTOM | ((deviceProfile.isLandscape) ? START : CENTER_HORIZONTAL);
+ bannerParams.gravity =
+ BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL);
// Set correct width
if (desiredTaskId == splitBounds.leftTopTaskId) {
@@ -795,9 +806,15 @@
}
@Override
- public Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
+ public float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
return dp.isLeftRightSplit
? floatingTask.getTranslationX()
: floatingTask.getTranslationY();
}
+
+ @NonNull
+ @Override
+ public LauncherAtom.TaskSwitcherContainer.OrientationHandler getHandlerTypeForLogging() {
+ return LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.java b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.java
deleted file mode 100644
index 4b65d53..0000000
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.java
+++ /dev/null
@@ -1,261 +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.orientation;
-
-
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ShapeDrawable;
-import android.util.FloatProperty;
-import android.util.Pair;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.quickstep.views.IconAppChipView;
-
-import java.util.List;
-
-/**
- * Abstraction layer to separate horizontal and vertical specific implementations
- * for {@link com.android.quickstep.views.RecentsView}. Majority of these implementations are
- * (should be) as simple as choosing the correct X and Y analogous methods.
- */
-public interface RecentsPagedOrientationHandler extends PagedOrientationHandler {
-
- RecentsPagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
- RecentsPagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
- RecentsPagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
-
- <T> void setSecondary(T target, Float2DAction<T> action, float param);
- <T> void set(T target, Int2DAction<T> action, int primaryParam, int secondaryParam);
- int getPrimarySize(View view);
- float getPrimarySize(RectF rect);
- int getSecondaryTranslationDirectionFactor();
- float getDegreesRotated();
- int getRotation();
- boolean isLayoutNaturalToLauncher();
-
- <T> T getPrimaryValue(T x, T y);
- <T> T getSecondaryValue(T x, T y);
- void setPrimaryScale(View view, float scale);
- void setSecondaryScale(View view, float scale);
- float getStart(RectF rect);
- float getEnd(RectF rect);
- int getClearAllSidePadding(View view, boolean isRtl);
- int getSecondaryDimension(View view);
- FloatProperty<View> getPrimaryViewTranslate();
- FloatProperty<View> getSecondaryViewTranslate();
- int getSplitTranslationDirectionFactor(@StagePosition int stagePosition,
- DeviceProfile deviceProfile);
- Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
- FloatProperty secondary, DeviceProfile deviceProfile);
- int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
- List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
- /**
- * @param placeholderHeight height of placeholder view in portrait, width in landscape
- */
- void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
- DeviceProfile dp, @StagePosition int stagePosition, Rect out);
-
- /**
- * Centers an icon in the split staging area, accounting for insets.
- * @param out The icon that needs to be centered.
- * @param onScreenRectCenterX The x-center of the on-screen staging area (most of the Rect is
- * offscreen).
- * @param onScreenRectCenterY The y-center of the on-screen staging area (most of the Rect is
- * offscreen).
- * @param fullscreenScaleX A x-scaling factor used to convert coordinates back into pixels.
- * @param fullscreenScaleY A y-scaling factor used to convert coordinates back into pixels.
- * @param drawableWidth The icon's drawable (final) width.
- * @param drawableHeight The icon's drawable (final) height.
- * @param dp The device profile, used to report rotation and hardware insets.
- * @param stagePosition 0 if the staging area is pinned to top/left, 1 for bottom/right.
- */
- void updateSplitIconParams(View out, float onScreenRectCenterX,
- float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
- int drawableWidth, int drawableHeight, DeviceProfile dp,
- @StagePosition int stagePosition);
-
- /**
- * Sets positioning and rotation for a SplitInstructionsView.
- * @param out The SplitInstructionsView that needs to be positioned.
- * @param dp The device profile, used to report rotation and device type.
- * @param splitInstructionsHeight The SplitInstructionView's height.
- * @param splitInstructionsWidth The SplitInstructionView's width.
- */
- void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
- int splitInstructionsWidth);
-
- /**
- * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
- * @param stagePosition the split position option (top/left, bottom/right) of the first
- * task selected for entering split
- * @param out1 the bounds for where the first selected app will be
- * @param out2 the bounds for where the second selected app will be, complimentary to
- * {@param out1} based on {@param initialSplitOption}
- */
- void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- @StagePosition int stagePosition, Rect out1, Rect out2);
-
- int getDefaultSplitPosition(DeviceProfile deviceProfile);
-
- /**
- * @param outRect This is expected to be the rect that has the dimensions for a non-split,
- * fullscreen task in overview. This will directly be modified.
- * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
- * outRect for
- */
- void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
- @SplitConfigurationOptions.StagePosition int desiredStagePosition);
-
- void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight,
- SplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl);
-
- /**
- * Creates two Points representing the dimensions of the two tasks in a GroupedTaskView
- *
- * @return first -> primary task snapshot, second -> secondary task snapshot.
- * x -> width, y -> height
- */
- Pair<Point, Point> getGroupedTaskViewSizes(DeviceProfile dp, SplitBounds splitBoundsConfig,
- int parentWidth, int parentHeight);
-
- // Overview TaskMenuView methods
- /** Sets layout params on a task's app icon. Only use this when app chip is disabled. */
- void setTaskIconParams(FrameLayout.LayoutParams iconParams,
- int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl);
-
- /**
- * Sets layout params on the children of an app chip. Only use this when app chip is enabled.
- */
- void setIconAppChipChildrenParams(
- FrameLayout.LayoutParams iconParams, int chipChildMarginStart);
-
- void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
- FrameLayout.LayoutParams iconMenuParams,
- int iconMenuMargin, int thumbnailTopMargin);
- void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
- int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig);
-
- /*
- * The following two methods try to center the TaskMenuView in landscape by finding the center
- * of the thumbnail view and then subtracting half of the taskMenu width. In this case, the
- * taskMenu width is the same size as the thumbnail width (what got set below in
- * getTaskMenuWidth()), so we directly use that in the calculations.
- */
- float getTaskMenuX(float x, View thumbnailView, DeviceProfile deviceProfile,
- float taskInsetMargin, View taskViewIcon);
- float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin, View taskViewIcon);
- int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
- @StagePosition int stagePosition);
-
- int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile, float taskMenuX,
- float taskMenuY);
- /**
- * Sets linear layout orientation for {@link com.android.launcher3.popup.SystemShortcut} items
- * inside task menu view.
- */
- void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
- LinearLayout taskMenuLayout, int dividerSpacing,
- ShapeDrawable dividerDrawable);
- /**
- * Sets layout param attributes for {@link com.android.launcher3.popup.SystemShortcut} child
- * views inside task menu view.
- */
- void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
- LinearLayout viewGroup, DeviceProfile deviceProfile);
-
- /**
- * Calculates the position where a Digital Wellbeing Banner should be placed on its parent
- * TaskView.
- * @return A Pair of Floats representing the proper x and y translations.
- */
- Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
- int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
- View[] thumbnailViews, int desiredTaskId, View banner);
-
- // The following are only used by TaskViewTouchHandler.
- /** @return Either VERTICAL or HORIZONTAL. */
- SingleAxisSwipeDetector.Direction getUpDownSwipeDirection();
- /** @return Given {@link #getUpDownSwipeDirection()}, whether POSITIVE or NEGATIVE is up. */
- int getUpDirection(boolean isRtl);
- /** @return Whether the displacement is going towards the top of the screen. */
- boolean isGoingUp(float displacement, boolean isRtl);
- /** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
- int getTaskDragDisplacementFactor(boolean isRtl);
-
- /**
- * Maps the velocity from the coordinate plane of the foreground app to that
- * of Launcher's (which now will always be portrait)
- */
- void adjustFloatingIconStartVelocity(PointF velocity);
-
- /**
- * Ensures that outStartRect left bound is within the DeviceProfile's visual boundaries
- * @param outStartRect The start rect that will directly be modified
- */
- void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile);
-
- /**
- * Determine the target translation for animating the FloatingTaskView out. This value could
- * either be an x-coordinate or a y-coordinate, depending on which way the FloatingTaskView was
- * docked.
- *
- * @param floatingTask The FloatingTaskView.
- * @param onScreenRect The current on-screen dimensions of the FloatingTaskView.
- * @param stagePosition STAGE_POSITION_TOP_OR_LEFT or STAGE_POSITION_BOTTOM_OR_RIGHT.
- * @param dp The device profile.
- * @return A float. When an animation translates the FloatingTaskView to this position, it will
- * appear to tuck away off the edge of the screen.
- */
- float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
- @StagePosition int stagePosition, DeviceProfile dp);
-
- /**
- * Sets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
- * either x or y), depending on how the view is oriented.
- *
- * @param floatingTask The FloatingTaskView to be translated.
- * @param translation The target translation value.
- * @param dp The current device profile.
- */
- void setFloatingTaskPrimaryTranslation(View floatingTask, float translation, DeviceProfile dp);
-
- /**
- * Gets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
- * either x or y), depending on how the view is oriented.
- *
- * @param floatingTask The FloatingTaskView in question.
- * @param dp The current device profile.
- * @return The current translation value.
- */
- Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp);
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
new file mode 100644
index 0000000..df4b030
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
@@ -0,0 +1,385 @@
+/*
+ * 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.orientation
+
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.ShapeDrawable
+import android.util.FloatProperty
+import android.util.Pair
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.touch.PagedOrientationHandler
+import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
+import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.quickstep.views.IconAppChipView
+
+/**
+ * Abstraction layer to separate horizontal and vertical specific implementations for
+ * [com.android.quickstep.views.RecentsView]. Majority of these implementations are (should be) as
+ * simple as choosing the correct X and Y analogous methods.
+ */
+interface RecentsPagedOrientationHandler : PagedOrientationHandler {
+ fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float)
+
+ operator fun <T> set(target: T, action: Int2DAction<T>, primaryParam: Int, secondaryParam: Int)
+
+ fun getPrimarySize(view: View): Int
+
+ fun getPrimarySize(rect: RectF): Float
+
+ val secondaryTranslationDirectionFactor: Int
+
+ val degreesRotated: Float
+
+ val rotation: Int
+
+ val isLayoutNaturalToLauncher: Boolean
+
+ fun <T> getPrimaryValue(x: T, y: T): T
+
+ fun <T> getSecondaryValue(x: T, y: T): T
+
+ fun setPrimaryScale(view: View, scale: Float)
+
+ fun setSecondaryScale(view: View, scale: Float)
+
+ fun getStart(rect: RectF): Float
+
+ fun getEnd(rect: RectF): Float
+
+ /** Rotate the provided insets to portrait perspective. */
+ fun rotateInsets(insets: Rect, outInsets: Rect)
+
+ fun getClearAllSidePadding(view: View, isRtl: Boolean): Int
+
+ fun getSecondaryDimension(view: View): Int
+
+ val primaryViewTranslate: FloatProperty<View>
+ val secondaryViewTranslate: FloatProperty<View>
+
+ fun getSplitTranslationDirectionFactor(
+ @StagePosition stagePosition: Int,
+ deviceProfile: DeviceProfile
+ ): Int
+
+ fun <T> getSplitSelectTaskOffset(
+ primary: FloatProperty<T>,
+ secondary: FloatProperty<T>,
+ deviceProfile: DeviceProfile
+ ): Pair<FloatProperty<T>, FloatProperty<T>>
+
+ fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int
+
+ fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption>
+
+ /** @param placeholderHeight height of placeholder view in portrait, width in landscape */
+ fun getInitialSplitPlaceholderBounds(
+ placeholderHeight: Int,
+ placeholderInset: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out: Rect
+ )
+
+ /**
+ * Centers an icon in the split staging area, accounting for insets.
+ *
+ * @param out The icon that needs to be centered.
+ * @param onScreenRectCenterX The x-center of the on-screen staging area (most of the Rect is
+ * offscreen).
+ * @param onScreenRectCenterY The y-center of the on-screen staging area (most of the Rect is
+ * offscreen).
+ * @param fullscreenScaleX A x-scaling factor used to convert coordinates back into pixels.
+ * @param fullscreenScaleY A y-scaling factor used to convert coordinates back into pixels.
+ * @param drawableWidth The icon's drawable (final) width.
+ * @param drawableHeight The icon's drawable (final) height.
+ * @param dp The device profile, used to report rotation and hardware insets.
+ * @param stagePosition 0 if the staging area is pinned to top/left, 1 for bottom/right.
+ */
+ fun updateSplitIconParams(
+ out: View,
+ onScreenRectCenterX: Float,
+ onScreenRectCenterY: Float,
+ fullscreenScaleX: Float,
+ fullscreenScaleY: Float,
+ drawableWidth: Int,
+ drawableHeight: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int
+ )
+
+ /**
+ * Sets positioning and rotation for a SplitInstructionsView.
+ *
+ * @param out The SplitInstructionsView that needs to be positioned.
+ * @param dp The device profile, used to report rotation and device type.
+ * @param splitInstructionsHeight The SplitInstructionView's height.
+ * @param splitInstructionsWidth The SplitInstructionView's width.
+ */
+ fun setSplitInstructionsParams(
+ out: View,
+ dp: DeviceProfile,
+ splitInstructionsHeight: Int,
+ splitInstructionsWidth: Int
+ )
+
+ /**
+ * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
+ * @param stagePosition the split position option (top/left, bottom/right) of the first task
+ * selected for entering split
+ * @param out1 the bounds for where the first selected app will be
+ * @param out2 the bounds for where the second selected app will be, complimentary to {@param
+ * out1} based on {@param initialSplitOption}
+ */
+ fun getFinalSplitPlaceholderBounds(
+ splitDividerSize: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out1: Rect,
+ out2: Rect
+ )
+
+ fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int
+
+ /**
+ * @param outRect This is expected to be the rect that has the dimensions for a non-split,
+ * fullscreen task in overview. This will directly be modified.
+ * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
+ * outRect for
+ */
+ fun setSplitTaskSwipeRect(
+ dp: DeviceProfile,
+ outRect: Rect,
+ splitInfo: SplitConfigurationOptions.SplitBounds,
+ @StagePosition desiredStagePosition: Int
+ )
+
+ fun measureGroupedTaskViewThumbnailBounds(
+ primarySnapshot: View,
+ secondarySnapshot: View,
+ parentWidth: Int,
+ parentHeight: Int,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+ dp: DeviceProfile,
+ isRtl: Boolean
+ )
+
+ /**
+ * Creates two Points representing the dimensions of the two tasks in a GroupedTaskView
+ *
+ * @return first -> primary task snapshot, second -> secondary task snapshot. x -> width, y ->
+ * height
+ */
+ fun getGroupedTaskViewSizes(
+ dp: DeviceProfile,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+ parentWidth: Int,
+ parentHeight: Int
+ ): Pair<Point, Point>
+ // Overview TaskMenuView methods
+ /** Sets layout params on a task's app icon. Only use this when app chip is disabled. */
+ fun setTaskIconParams(
+ iconParams: FrameLayout.LayoutParams,
+ taskIconMargin: Int,
+ taskIconHeight: Int,
+ thumbnailTopMargin: Int,
+ isRtl: Boolean
+ )
+
+ /**
+ * Sets layout params on the children of an app chip. Only use this when app chip is enabled.
+ */
+ fun setIconAppChipChildrenParams(
+ iconParams: FrameLayout.LayoutParams,
+ chipChildMarginStart: Int
+ )
+
+ fun setIconAppChipMenuParams(
+ iconAppChipView: IconAppChipView,
+ iconMenuParams: FrameLayout.LayoutParams,
+ iconMenuMargin: Int,
+ thumbnailTopMargin: Int
+ )
+
+ fun setSplitIconParams(
+ primaryIconView: View,
+ secondaryIconView: View,
+ taskIconHeight: Int,
+ primarySnapshotWidth: Int,
+ primarySnapshotHeight: Int,
+ groupedTaskViewHeight: Int,
+ groupedTaskViewWidth: Int,
+ isRtl: Boolean,
+ deviceProfile: DeviceProfile,
+ splitConfig: SplitConfigurationOptions.SplitBounds
+ )
+
+ /*
+ * The following two methods try to center the TaskMenuView in landscape by finding the center
+ * of the thumbnail view and then subtracting half of the taskMenu width. In this case, the
+ * taskMenu width is the same size as the thumbnail width (what got set below in
+ * getTaskMenuWidth()), so we directly use that in the calculations.
+ */
+ fun getTaskMenuX(
+ x: Float,
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float
+
+ fun getTaskMenuY(
+ y: Float,
+ thumbnailView: View,
+ stagePosition: Int,
+ taskMenuView: View,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float
+
+ fun getTaskMenuWidth(
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ @StagePosition stagePosition: Int
+ ): Int
+
+ fun getTaskMenuHeight(
+ taskInsetMargin: Float,
+ deviceProfile: DeviceProfile,
+ taskMenuX: Float,
+ taskMenuY: Float
+ ): Int
+
+ /**
+ * Sets linear layout orientation for [com.android.launcher3.popup.SystemShortcut] items inside
+ * task menu view.
+ */
+ fun setTaskOptionsMenuLayoutOrientation(
+ deviceProfile: DeviceProfile,
+ taskMenuLayout: LinearLayout,
+ dividerSpacing: Int,
+ dividerDrawable: ShapeDrawable
+ )
+
+ /**
+ * Sets layout param attributes for [com.android.launcher3.popup.SystemShortcut] child views
+ * inside task menu view.
+ */
+ fun setLayoutParamsForTaskMenuOptionItem(
+ lp: LinearLayout.LayoutParams,
+ viewGroup: LinearLayout,
+ deviceProfile: DeviceProfile
+ )
+
+ /**
+ * Calculates the position where a Digital Wellbeing Banner should be placed on its parent
+ * TaskView.
+ *
+ * @return A Pair of Floats representing the proper x and y translations.
+ */
+ fun getDwbLayoutTranslations(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ splitBounds: SplitConfigurationOptions.SplitBounds?,
+ deviceProfile: DeviceProfile,
+ thumbnailViews: Array<View>,
+ desiredTaskId: Int,
+ banner: View
+ ): Pair<Float, Float>
+ // The following are only used by TaskViewTouchHandler.
+
+ /** @return Either VERTICAL or HORIZONTAL. */
+ val upDownSwipeDirection: SingleAxisSwipeDetector.Direction
+
+ /** @return Given [.getUpDownSwipeDirection], whether POSITIVE or NEGATIVE is up. */
+ fun getUpDirection(isRtl: Boolean): Int
+
+ /** @return Whether the displacement is going towards the top of the screen. */
+ fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean
+
+ /** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
+ fun getTaskDragDisplacementFactor(isRtl: Boolean): Int
+
+ /**
+ * Maps the velocity from the coordinate plane of the foreground app to that of Launcher's
+ * (which now will always be portrait)
+ */
+ fun adjustFloatingIconStartVelocity(velocity: PointF)
+
+ /**
+ * Ensures that outStartRect left bound is within the DeviceProfile's visual boundaries
+ *
+ * @param outStartRect The start rect that will directly be modified
+ */
+ fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile)
+
+ /**
+ * Determine the target translation for animating the FloatingTaskView out. This value could
+ * either be an x-coordinate or a y-coordinate, depending on which way the FloatingTaskView was
+ * docked.
+ *
+ * @param floatingTask The FloatingTaskView.
+ * @param onScreenRect The current on-screen dimensions of the FloatingTaskView.
+ * @param stagePosition STAGE_POSITION_TOP_OR_LEFT or STAGE_POSITION_BOTTOM_OR_RIGHT.
+ * @param dp The device profile.
+ * @return A float. When an animation translates the FloatingTaskView to this position, it will
+ * appear to tuck away off the edge of the screen.
+ */
+ fun getFloatingTaskOffscreenTranslationTarget(
+ floatingTask: View,
+ onScreenRect: RectF,
+ @StagePosition stagePosition: Int,
+ dp: DeviceProfile
+ ): Float
+
+ /**
+ * Sets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
+ * either x or y), depending on how the view is oriented.
+ *
+ * @param floatingTask The FloatingTaskView to be translated.
+ * @param translation The target translation value.
+ * @param dp The current device profile.
+ */
+ fun setFloatingTaskPrimaryTranslation(floatingTask: View, translation: Float, dp: DeviceProfile)
+
+ /**
+ * Gets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
+ * either x or y), depending on how the view is oriented.
+ *
+ * @param floatingTask The FloatingTaskView in question.
+ * @param dp The current device profile.
+ * @return The current translation value.
+ */
+ fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float
+
+ fun getHandlerTypeForLogging(): LauncherAtom.TaskSwitcherContainer.OrientationHandler
+
+ companion object {
+ @JvmField val PORTRAIT: RecentsPagedOrientationHandler = PortraitPagedViewHandler()
+ @JvmField val LANDSCAPE: RecentsPagedOrientationHandler = LandscapePagedViewHandler()
+ @JvmField val SEASCAPE: RecentsPagedOrientationHandler = SeascapePagedViewHandler()
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
deleted file mode 100644
index 89c678c..0000000
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
+++ /dev/null
@@ -1,418 +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.orientation;
-
-import static android.view.Gravity.BOTTOM;
-import static android.view.Gravity.CENTER_VERTICAL;
-import static android.view.Gravity.END;
-import static android.view.Gravity.RIGHT;
-import static android.view.Gravity.START;
-import static android.view.Gravity.TOP;
-import static android.view.View.LAYOUT_DIRECTION_RTL;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
-
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.util.Pair;
-import android.view.Gravity;
-import android.view.Surface;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.views.IconAppChipView;
-
-import java.util.Collections;
-import java.util.List;
-
-public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
-
- @Override
- public int getSecondaryTranslationDirectionFactor() {
- return -1;
- }
-
- @Override
- public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
- if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
- return -1;
- } else {
- return 1;
- }
- }
-
- @Override
- public boolean getRecentsRtlSetting(Resources resources) {
- return Utilities.isRtl(resources);
- }
-
- @Override
- public float getDegreesRotated() {
- return 270;
- }
-
- @Override
- public int getRotation() {
- return Surface.ROTATION_270;
- }
-
- @Override
- public void adjustFloatingIconStartVelocity(PointF velocity) {
- float oldX = velocity.x;
- float oldY = velocity.y;
- velocity.set(oldY, -oldX);
- }
-
- @Override
- public float getTaskMenuX(float x, View thumbnailView,
- DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
- return x + taskInsetMargin;
- }
-
- @Override
- public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin, View taskViewIcon) {
- if (enableOverviewIconMenu()) {
- return y;
- }
- BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskMenuView.getLayoutParams();
- int taskMenuWidth = lp.width;
- if (stagePosition == STAGE_POSITION_UNDEFINED) {
- return y + taskInsetMargin
- + (thumbnailView.getMeasuredHeight() + taskMenuWidth) / 2f;
- } else {
- return y + taskMenuWidth + taskInsetMargin;
- }
- }
-
- @Override
- public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
- float taskMenuX, float taskMenuY) {
- return (int) (deviceProfile.availableWidthPx - taskInsetMargin - taskMenuX);
- }
-
- @Override
- public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
- int desiredStagePosition) {
- float topLeftTaskPercent = splitInfo.appsStackedVertically
- ? splitInfo.topTaskPercent
- : splitInfo.leftTaskPercent;
- float dividerBarPercent = splitInfo.appsStackedVertically
- ? splitInfo.dividerHeightPercent
- : splitInfo.dividerWidthPercent;
-
- // In seascape, the primary thumbnail is counterintuitively placed at the physical bottom of
- // the screen. This is to preserve consistency when the user rotates: From the user's POV,
- // the primary should always be on the left.
- if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
- outRect.top += (int) (outRect.height() * ((1 - topLeftTaskPercent)));
- } else {
- outRect.bottom -= (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
- }
- }
-
- @Override
- public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
- int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
- View[] thumbnailViews, int desiredTaskId, View banner) {
- boolean isRtl = banner.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- float translationX = 0;
- float translationY = 0;
- FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
- banner.setPivotX(0);
- banner.setPivotY(0);
- banner.setRotation(getDegreesRotated());
- translationX = taskViewWidth - banner.getHeight();
- FrameLayout.LayoutParams snapshotParams =
- (FrameLayout.LayoutParams) thumbnailViews[0]
- .getLayoutParams();
- bannerParams.gravity = BOTTOM | (isRtl ? END : START);
-
- if (splitBounds == null) {
- // Single, fullscreen case
- bannerParams.width = taskViewHeight - snapshotParams.topMargin;
- translationY = banner.getHeight();
- return new Pair<>(translationX, translationY);
- }
-
- // Set correct width
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- bannerParams.width = thumbnailViews[0].getMeasuredHeight();
- } else {
- bannerParams.width = thumbnailViews[1].getMeasuredHeight();
- }
-
- // Set translations
- if (desiredTaskId == splitBounds.rightBottomTaskId) {
- translationY = banner.getHeight();
- }
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
- ? (1f - splitBounds.topTaskPercent)
- : (1f - splitBounds.leftTaskPercent);
- translationY = banner.getHeight()
- - ((taskViewHeight - snapshotParams.topMargin)
- * bottomRightTaskPlusDividerPercent);
- }
- return new Pair<>(translationX, translationY);
- }
-
- @Override
- public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
- return dp.widthPx - rect.right;
- }
-
- @Override
- public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
- // Add "right" option which is actually the top
- return Collections.singletonList(new SplitPositionOption(
- R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
- STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
- }
-
- @Override
- public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
- int splitInstructionsWidth) {
- out.setPivotX(0);
- out.setPivotY(splitInstructionsHeight);
- out.setRotation(getDegreesRotated());
- int distanceToEdge = out.getResources().getDimensionPixelSize(
- R.dimen.split_instructions_bottom_margin_phone_landscape);
- // Adjust for any insets on the right edge
- int insetCorrectionX = dp.getInsets().right;
- // Center the view in case of unbalanced insets on top or bottom of screen
- int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
- out.setTranslationX(splitInstructionsWidth - distanceToEdge + insetCorrectionX);
- out.setTranslationY(((-splitInstructionsHeight + splitInstructionsWidth) / 2f)
- + insetCorrectionY);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
- // Setting gravity to RIGHT instead of the lint-recommended END because we always want this
- // view to be screen-right when phone is in seascape, regardless of the RtL setting.
- lp.gravity = RIGHT | CENTER_VERTICAL;
- out.setLayoutParams(lp);
- }
-
- @Override
- public void setTaskIconParams(FrameLayout.LayoutParams iconParams,
- int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
- iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
- iconParams.setMargins(-taskIconHeight - taskIconMargin / 2, thumbnailTopMargin / 2, 0, 0);
- }
-
- @Override
- public void setIconAppChipChildrenParams(FrameLayout.LayoutParams iconParams,
- int chipChildMarginStart) {
- iconParams.setMargins(0, 0, 0, 0);
- iconParams.setMarginStart(chipChildMarginStart);
- iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
- }
-
- @Override
- public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
- FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
- boolean isRtl = iconAppChipView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- iconMenuParams.gravity = (isRtl ? TOP : BOTTOM) | (isRtl ? END : START);
- iconMenuParams.setMarginStart(0);
- iconMenuParams.topMargin = isRtl ? iconMenuMargin : 0;
- iconMenuParams.bottomMargin = 0;
- iconMenuParams.setMarginEnd(isRtl ? thumbnailTopMargin : 0);
-
- // Use half menu height to place the pivot within the X/Y center of icon in the menu.
- float iconCenter = iconAppChipView.getHeight() / 2f;
- iconAppChipView.setPivotX(isRtl ? iconMenuParams.width / 2f : iconCenter);
- iconAppChipView.setPivotY(
- isRtl ? iconMenuParams.width / 2f : iconCenter - iconMenuMargin);
- iconAppChipView.setSplitTranslationY(0);
- iconAppChipView.setRotation(getDegreesRotated());
- }
-
- @Override
- public void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
- int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig) {
- super.setSplitIconParams(primaryIconView, secondaryIconView, taskIconHeight,
- primarySnapshotWidth, primarySnapshotHeight, groupedTaskViewHeight,
- groupedTaskViewWidth, isRtl, deviceProfile, splitConfig);
- FrameLayout.LayoutParams primaryIconParams =
- (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
- FrameLayout.LayoutParams secondaryIconParams =
- (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams();
-
- // We calculate the "midpoint" of the thumbnail area, and place the icons there.
- // This is the place where the thumbnail area splits by default, in a near-50/50 split.
- // It is usually not exactly 50/50, due to insets/screen cutouts.
- int fullscreenInsetThickness = deviceProfile.getInsets().top
- - deviceProfile.getInsets().bottom;
- int fullscreenMidpointFromBottom = ((deviceProfile.heightPx
- - fullscreenInsetThickness) / 2);
- float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
- float insetPct = (float) fullscreenInsetThickness / deviceProfile.heightPx;
- int spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx;
- int overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots;
- int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness * midpointFromBottomPct);
- int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
-
- int gravity = (isRtl ? TOP : BOTTOM) | (isRtl ? END : START);
- primaryIconParams.gravity = gravity;
- secondaryIconParams.gravity = gravity;
- primaryIconView.setTranslationX(0);
- secondaryIconView.setTranslationX(0);
- if (enableOverviewIconMenu()) {
- IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
- IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
- if (isRtl) {
- primaryAppChipView.setSplitTranslationY(
- groupedTaskViewHeight - primarySnapshotHeight);
- secondaryAppChipView.setSplitTranslationY(0);
- } else {
- secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight);
- primaryAppChipView.setSplitTranslationY(0);
- }
- } else if (splitConfig.initiatedFromSeascape) {
- // if the split was initiated from seascape,
- // the task on the right (secondary) is slightly larger
- if (isRtl) {
- primaryIconView.setTranslationY(bottomToMidpointOffset - insetOffset
- + taskIconHeight);
- secondaryIconView.setTranslationY(bottomToMidpointOffset - insetOffset);
- } else {
- primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
- + taskIconHeight);
- secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
- }
- } else {
- // if not,
- // the task on the left (primary) is slightly larger
- if (isRtl) {
- primaryIconView.setTranslationY(bottomToMidpointOffset + taskIconHeight);
- secondaryIconView.setTranslationY(bottomToMidpointOffset);
- } else {
- primaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
- secondaryIconView.setTranslationY(-bottomToMidpointOffset);
- }
- }
-
- primaryIconView.setLayoutParams(primaryIconParams);
- secondaryIconView.setLayoutParams(secondaryIconParams);
- }
-
- @Override
- public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp,
- boolean isRtl) {
- FrameLayout.LayoutParams primaryParams =
- (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
- FrameLayout.LayoutParams secondaryParams =
- (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
-
- // Swap the margins that are set in TaskView#setRecentsOrientedState()
- secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx;
- primaryParams.topMargin = 0;
-
- // Measure and layout the thumbnails bottom up, since the primary is on the visual left
- // (portrait bottom) and secondary is on the right (portrait top)
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.dividerHeightPercent
- : splitBoundsConfig.dividerWidthPercent));
-
- Pair<Point, Point> taskViewSizes =
- getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
-
- secondarySnapshot.setTranslationY(0);
- primarySnapshot.setTranslationY(taskViewSizes.second.y + spaceAboveSnapshot + dividerBar);
-
- primarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY)
- );
- secondarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y, View.MeasureSpec.EXACTLY)
- );
- }
-
- @Override
- public Pair<Point, Point> getGroupedTaskViewSizes(
- DeviceProfile dp,
- SplitBounds splitBoundsConfig,
- int parentWidth,
- int parentHeight) {
- // Measure and layout the thumbnails bottom up, since the primary is on the visual left
- // (portrait bottom) and secondary is on the right (portrait top)
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.dividerHeightPercent
- : splitBoundsConfig.dividerWidthPercent));
- float taskPercent = splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.topTaskPercent
- : splitBoundsConfig.leftTaskPercent;
-
- Point firstTaskViewSize = new Point(
- parentWidth,
- (int) (totalThumbnailHeight * taskPercent)
- );
- Point secondTaskViewSize = new Point(
- parentWidth,
- totalThumbnailHeight - firstTaskViewSize.y - dividerBar
- );
-
- return new Pair<>(firstTaskViewSize, secondTaskViewSize);
- }
-
- /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
-
- @Override
- public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
- return HORIZONTAL;
- }
-
- @Override
- public int getUpDirection(boolean isRtl) {
- return isRtl ? SingleAxisSwipeDetector.DIRECTION_POSITIVE
- : SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
- }
-
- @Override
- public boolean isGoingUp(float displacement, boolean isRtl) {
- return isRtl ? displacement > 0 : displacement < 0;
- }
-
- @Override
- public int getTaskDragDisplacementFactor(boolean isRtl) {
- return isRtl ? -1 : 1;
- }
-
- /* -------------------- */
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
new file mode 100644
index 0000000..333359f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -0,0 +1,407 @@
+/*
+ * 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.orientation
+
+import android.annotation.SuppressLint
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.util.Pair
+import android.view.Gravity
+import android.view.Surface
+import android.view.View
+import android.view.View.MeasureSpec
+import android.widget.FrameLayout
+import androidx.core.util.component1
+import androidx.core.util.component2
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
+import com.android.launcher3.views.BaseDragLayer
+import com.android.quickstep.views.IconAppChipView
+
+class SeascapePagedViewHandler : LandscapePagedViewHandler() {
+ override fun rotateInsets(insets: Rect, outInsets: Rect) {
+ outInsets.set(insets.top, insets.right, insets.bottom, insets.left)
+ }
+
+ override val secondaryTranslationDirectionFactor: Int = -1
+
+ override fun getSplitTranslationDirectionFactor(
+ stagePosition: Int,
+ deviceProfile: DeviceProfile
+ ): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
+
+ override fun getRecentsRtlSetting(resources: Resources): Boolean = Utilities.isRtl(resources)
+
+ override val degreesRotated: Float = 270f
+
+ override val rotation: Int = Surface.ROTATION_270
+
+ override fun adjustFloatingIconStartVelocity(velocity: PointF) =
+ velocity.set(velocity.y, -velocity.x)
+
+ override fun getTaskMenuX(
+ x: Float,
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float = x + taskInsetMargin
+
+ override fun getTaskMenuY(
+ y: Float,
+ thumbnailView: View,
+ stagePosition: Int,
+ taskMenuView: View,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float {
+ if (Flags.enableOverviewIconMenu()) {
+ return y
+ }
+ val lp = taskMenuView.layoutParams as BaseDragLayer.LayoutParams
+ val taskMenuWidth = lp.width
+ return if (stagePosition == STAGE_POSITION_UNDEFINED) {
+ y + taskInsetMargin + (thumbnailView.measuredHeight + taskMenuWidth) / 2f
+ } else {
+ y + taskMenuWidth + taskInsetMargin
+ }
+ }
+
+ override fun getTaskMenuHeight(
+ taskInsetMargin: Float,
+ deviceProfile: DeviceProfile,
+ taskMenuX: Float,
+ taskMenuY: Float
+ ): Int = (deviceProfile.availableWidthPx - taskInsetMargin - taskMenuX).toInt()
+
+ override fun setSplitTaskSwipeRect(
+ dp: DeviceProfile,
+ outRect: Rect,
+ splitInfo: SplitBounds,
+ desiredStagePosition: Int
+ ) {
+ val topLeftTaskPercent: Float
+ val dividerBarPercent: Float
+ if (splitInfo.appsStackedVertically) {
+ topLeftTaskPercent = splitInfo.topTaskPercent
+ dividerBarPercent = splitInfo.dividerHeightPercent
+ } else {
+ topLeftTaskPercent = splitInfo.leftTaskPercent
+ dividerBarPercent = splitInfo.dividerWidthPercent
+ }
+
+ // In seascape, the primary thumbnail is counterintuitively placed at the physical bottom of
+ // the screen. This is to preserve consistency when the user rotates: From the user's POV,
+ // the primary should always be on the left.
+ if (desiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ outRect.top += (outRect.height() * (1 - topLeftTaskPercent)).toInt()
+ } else {
+ outRect.bottom -= (outRect.height() * (topLeftTaskPercent + dividerBarPercent)).toInt()
+ }
+ }
+
+ override fun getDwbLayoutTranslations(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ splitBounds: SplitBounds?,
+ deviceProfile: DeviceProfile,
+ thumbnailViews: Array<View>,
+ desiredTaskId: Int,
+ banner: View
+ ): Pair<Float, Float> {
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+ val isRtl = banner.layoutDirection == View.LAYOUT_DIRECTION_RTL
+
+ val bannerParams = banner.layoutParams as FrameLayout.LayoutParams
+ bannerParams.gravity = Gravity.BOTTOM or if (isRtl) Gravity.END else Gravity.START
+ banner.pivotX = 0f
+ banner.pivotY = 0f
+ banner.rotation = degreesRotated
+
+ val translationX: Float = (taskViewWidth - banner.height).toFloat()
+ if (splitBounds == null) {
+ // Single, fullscreen case
+ bannerParams.width = taskViewHeight - snapshotParams.topMargin
+ return Pair(translationX, banner.height.toFloat())
+ }
+
+ // Set correct width and translations
+ val translationY: Float
+ if (desiredTaskId == splitBounds.leftTopTaskId) {
+ bannerParams.width = thumbnailViews[0].measuredHeight
+ val bottomRightTaskPlusDividerPercent =
+ if (splitBounds.appsStackedVertically) {
+ 1f - splitBounds.topTaskPercent
+ } else {
+ 1f - splitBounds.leftTaskPercent
+ }
+ translationY =
+ banner.height -
+ (taskViewHeight - snapshotParams.topMargin) * bottomRightTaskPlusDividerPercent
+ } else {
+ bannerParams.width = thumbnailViews[1].measuredHeight
+ translationY = banner.height.toFloat()
+ }
+
+ return Pair(translationX, translationY)
+ }
+
+ override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int =
+ dp.widthPx - rect.right
+
+ override fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption> =
+ // Add "right" option which is actually the top
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_horizontal,
+ R.string.recent_task_option_split_screen,
+ STAGE_POSITION_BOTTOM_OR_RIGHT,
+ STAGE_TYPE_MAIN
+ )
+ )
+
+ override fun setSplitInstructionsParams(
+ out: View,
+ dp: DeviceProfile,
+ splitInstructionsHeight: Int,
+ splitInstructionsWidth: Int
+ ) {
+ out.pivotX = 0f
+ out.pivotY = splitInstructionsHeight.toFloat()
+ out.rotation = degreesRotated
+ val distanceToEdge =
+ out.resources.getDimensionPixelSize(
+ R.dimen.split_instructions_bottom_margin_phone_landscape
+ )
+ // Adjust for any insets on the right edge
+ val insetCorrectionX = dp.insets.right
+ // Center the view in case of unbalanced insets on top or bottom of screen
+ val insetCorrectionY = (dp.insets.bottom - dp.insets.top) / 2
+ out.translationX = (splitInstructionsWidth - distanceToEdge + insetCorrectionX).toFloat()
+ out.translationY =
+ (-splitInstructionsHeight + splitInstructionsWidth) / 2f + insetCorrectionY
+ // Setting gravity to RIGHT instead of the lint-recommended END because we always want this
+ // view to be screen-right when phone is in seascape, regardless of the RtL setting.
+ val lp = out.layoutParams as FrameLayout.LayoutParams
+ lp.gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
+ out.layoutParams = lp
+ }
+
+ override fun setTaskIconParams(
+ iconParams: FrameLayout.LayoutParams,
+ taskIconMargin: Int,
+ taskIconHeight: Int,
+ thumbnailTopMargin: Int,
+ isRtl: Boolean
+ ) {
+ iconParams.gravity =
+ if (isRtl) {
+ Gravity.END or Gravity.CENTER_VERTICAL
+ } else {
+ Gravity.START or Gravity.CENTER_VERTICAL
+ }
+ iconParams.setMargins(-taskIconHeight - taskIconMargin / 2, thumbnailTopMargin / 2, 0, 0)
+ }
+
+ override fun setIconAppChipChildrenParams(
+ iconParams: FrameLayout.LayoutParams,
+ chipChildMarginStart: Int
+ ) {
+ iconParams.setMargins(0, 0, 0, 0)
+ iconParams.marginStart = chipChildMarginStart
+ iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+ }
+
+ override fun setIconAppChipMenuParams(
+ iconAppChipView: IconAppChipView,
+ iconMenuParams: FrameLayout.LayoutParams,
+ iconMenuMargin: Int,
+ thumbnailTopMargin: Int
+ ) {
+ val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ val iconCenter = iconAppChipView.getHeight() / 2f
+
+ if (isRtl) {
+ iconMenuParams.gravity = Gravity.TOP or Gravity.END
+ iconMenuParams.topMargin = iconMenuMargin
+ iconMenuParams.marginEnd = thumbnailTopMargin
+ // Use half menu height to place the pivot within the X/Y center of icon in the menu.
+ iconAppChipView.pivotX = iconMenuParams.width / 2f
+ iconAppChipView.pivotY = iconMenuParams.width / 2f
+ } else {
+ iconMenuParams.gravity = Gravity.BOTTOM or Gravity.START
+ iconMenuParams.topMargin = 0
+ iconMenuParams.marginEnd = 0
+ iconAppChipView.pivotX = iconCenter
+ iconAppChipView.pivotY = iconCenter - iconMenuMargin
+ }
+ iconMenuParams.marginStart = 0
+ iconMenuParams.bottomMargin = 0
+ iconAppChipView.setSplitTranslationY(0f)
+ iconAppChipView.setRotation(degreesRotated)
+ }
+
+ override fun measureGroupedTaskViewThumbnailBounds(
+ primarySnapshot: View,
+ secondarySnapshot: View,
+ parentWidth: Int,
+ parentHeight: Int,
+ splitBoundsConfig: SplitBounds,
+ dp: DeviceProfile,
+ isRtl: Boolean
+ ) {
+ val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
+ val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
+
+ // Swap the margins that are set in TaskView#setRecentsOrientedState()
+ secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx
+ primaryParams.topMargin = 0
+
+ // Measure and layout the thumbnails bottom up, since the primary is on the visual left
+ // (portrait bottom) and secondary is on the right (portrait top)
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
+
+ val (taskViewFirst, taskViewSecond) =
+ getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
+ secondarySnapshot.translationY = 0f
+ primarySnapshot.translationY =
+ (taskViewSecond.y + spaceAboveSnapshot + dividerBar).toFloat()
+ primarySnapshot.measure(
+ MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+ )
+ secondarySnapshot.measure(
+ MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+ )
+ }
+
+ override fun getGroupedTaskViewSizes(
+ dp: DeviceProfile,
+ splitBoundsConfig: SplitBounds,
+ parentWidth: Int,
+ parentHeight: Int
+ ): Pair<Point, Point> {
+ // Measure and layout the thumbnails bottom up, since the primary is on the visual left
+ // (portrait bottom) and secondary is on the right (portrait top)
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
+
+ val taskPercent =
+ if (splitBoundsConfig.appsStackedVertically) {
+ splitBoundsConfig.topTaskPercent
+ } else {
+ splitBoundsConfig.leftTaskPercent
+ }
+ val firstTaskViewSize = Point(parentWidth, (totalThumbnailHeight * taskPercent).toInt())
+ val secondTaskViewSize =
+ Point(parentWidth, totalThumbnailHeight - firstTaskViewSize.y - dividerBar)
+ return Pair(firstTaskViewSize, secondTaskViewSize)
+ }
+
+ /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+ override val upDownSwipeDirection: SingleAxisSwipeDetector.Direction =
+ SingleAxisSwipeDetector.HORIZONTAL
+
+ override fun getUpDirection(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
+
+ override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) -1 else 1
+ /* -------------------- */
+
+ override fun getSplitIconsPosition(
+ taskIconHeight: Int,
+ primarySnapshotHeight: Int,
+ totalThumbnailHeight: Int,
+ isRtl: Boolean,
+ overviewTaskMarginPx: Int,
+ dividerSize: Int,
+ ): SplitIconPositions {
+ return if (Flags.enableOverviewIconMenu()) {
+ if (isRtl) {
+ SplitIconPositions(
+ topLeftY = totalThumbnailHeight - primarySnapshotHeight,
+ bottomRightY = 0
+ )
+ } else {
+ SplitIconPositions(
+ topLeftY = 0,
+ bottomRightY = -(primarySnapshotHeight + dividerSize)
+ )
+ }
+ } else {
+ // In seascape, the icons are initially placed at the bottom start of the
+ // display (portrait locked). The values defined here are used to translate the icons
+ // from the bottom to the almost-center of the screen using the bottom margin.
+ // The primary snapshot is placed at the bottom, thus we translate the icons using
+ // the size of the primary snapshot minus the icon size for the top-left icon.
+ SplitIconPositions(
+ topLeftY = primarySnapshotHeight - taskIconHeight,
+ bottomRightY = primarySnapshotHeight + dividerSize
+ )
+ }
+ }
+
+ /**
+ * Updates icon view gravity and translation for split tasks
+ *
+ * @param iconView View to be updated
+ * @param translationY the translationY that should be applied
+ * @param isRtl Whether the layout direction is RTL (or false for LTR).
+ */
+ @SuppressLint("RtlHardcoded")
+ override fun updateSplitIconsPosition(iconView: View, translationY: Int, isRtl: Boolean) {
+ val layoutParams = iconView.layoutParams as FrameLayout.LayoutParams
+
+ if (Flags.enableOverviewIconMenu()) {
+ val appChipView = iconView as IconAppChipView
+ layoutParams.gravity =
+ if (isRtl) Gravity.TOP or Gravity.END else Gravity.BOTTOM or Gravity.START
+ appChipView.layoutParams = layoutParams
+ appChipView.setSplitTranslationX(0f)
+ appChipView.setSplitTranslationY(translationY.toFloat())
+ } else {
+ layoutParams.gravity = Gravity.BOTTOM or Gravity.LEFT
+ iconView.translationX = 0f
+ iconView.translationY = 0f
+ layoutParams.bottomMargin = translationY
+ iconView.layoutParams = layoutParams
+ }
+ }
+
+ @Override
+ override fun getHandlerTypeForLogging(): LauncherAtom.TaskSwitcherContainer.OrientationHandler =
+ LauncherAtom.TaskSwitcherContainer.OrientationHandler.SEASCAPE
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksDataSource.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksDataSource.kt
new file mode 100644
index 0000000..6719099
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksDataSource.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import com.android.quickstep.util.GroupTask
+import java.util.function.Consumer
+
+interface RecentTasksDataSource {
+ fun getTasks(callback: Consumer<List<GroupTask>>?): Int
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
new file mode 100644
index 0000000..c1eef0b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import com.android.systemui.shared.recents.model.Task
+import kotlinx.coroutines.flow.Flow
+
+interface RecentTasksRepository {
+ /** Gets all the recent tasks, refreshing from data sources if [forceRefresh] is true. */
+ fun getAllTaskData(forceRefresh: Boolean = false): Flow<List<Task>>
+
+ /**
+ * Gets the data associated with a task that has id [taskId]. Flow will settle on null if the
+ * task was not found.
+ */
+ fun getTaskDataById(taskId: Int): Flow<Task?>
+
+ /**
+ * Sets the tasks that are visible, indicating that properties relating to visuals need to be
+ * populated e.g. icons/thumbnails etc.
+ */
+ fun setVisibleTasks(visibleTaskIdList: List<Int>)
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
new file mode 100644
index 0000000..b21a1b4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import com.android.quickstep.TaskIconCache
+import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
+import com.android.quickstep.util.GroupTask
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import kotlin.coroutines.resume
+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.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class TasksRepository(
+ private val recentsModel: RecentTasksDataSource,
+ private val taskThumbnailDataSource: TaskThumbnailDataSource,
+ private val taskIconCache: TaskIconCache,
+) : RecentTasksRepository {
+ private val groupedTaskData = MutableStateFlow(emptyList<GroupTask>())
+ private val _taskData =
+ groupedTaskData.map { groupTaskList -> groupTaskList.flatMap { it.tasks } }
+ private val visibleTaskIds = MutableStateFlow(emptySet<Int>())
+
+ private val taskData: Flow<List<Task>> =
+ combine(_taskData, getThumbnailQueryResults()) { tasks, results ->
+ tasks.forEach { task ->
+ // Add retrieved thumbnails + remove unnecessary thumbnails
+ task.thumbnail = results[task.key.id]
+ }
+ tasks
+ }
+
+ override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> {
+ if (forceRefresh) {
+ recentsModel.getTasks { groupedTaskData.value = it }
+ }
+ return taskData
+ }
+
+ override fun getTaskDataById(taskId: Int): Flow<Task?> =
+ taskData.map { taskList -> taskList.firstOrNull { it.key.id == taskId } }
+
+ override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
+ this.visibleTaskIds.value = visibleTaskIdList.toSet()
+ }
+
+ /** Flow wrapper for [TaskThumbnailDataSource.updateThumbnailInBackground] api */
+ private fun getThumbnailDataRequest(task: Task): ThumbnailDataRequest =
+ flow {
+ emit(task.key.id to task.thumbnail)
+ val thumbnailDataResult: ThumbnailData? =
+ suspendCancellableCoroutine { continuation ->
+ val cancellableTask =
+ taskThumbnailDataSource.updateThumbnailInBackground(task) {
+ continuation.resume(it)
+ }
+ continuation.invokeOnCancellation { cancellableTask?.cancel() }
+ }
+ emit(task.key.id to thumbnailDataResult)
+ }
+ .distinctUntilChanged()
+
+ /**
+ * This is a Flow that makes a query for thumbnail data to the [taskThumbnailDataSource] for
+ * each visible task. It then collects the responses and returns them in a Map as soon as they
+ * are available.
+ */
+ private fun getThumbnailQueryResults(): Flow<Map<Int, ThumbnailData?>> {
+ val visibleTasks =
+ combine(_taskData, visibleTaskIds) { tasks, visibleIds ->
+ tasks.filter { it.key.id in visibleIds }
+ }
+ val visibleThumbnailDataRequests: Flow<List<ThumbnailDataRequest>> =
+ visibleTasks.map {
+ it.map { visibleTask ->
+ val taskCopy = Task(visibleTask).apply { thumbnail = visibleTask.thumbnail }
+ getThumbnailDataRequest(taskCopy)
+ }
+ }
+ return visibleThumbnailDataRequests.flatMapLatest {
+ thumbnailRequestFlows: List<ThumbnailDataRequest> ->
+ if (thumbnailRequestFlows.isEmpty()) {
+ flowOf(emptyMap())
+ } else {
+ combine(thumbnailRequestFlows) { it.toMap() }
+ }
+ }
+ }
+}
+
+typealias ThumbnailDataRequest = Flow<Pair<Int, ThumbnailData?>>
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
new file mode 100644
index 0000000..28212cf
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.viewmodel
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+// This is far from complete but serves the purpose of enabling refactoring in other areas
+class RecentsViewData {
+ val fullscreenProgress = MutableStateFlow(1f)
+
+ // This is typically a View concern but it is used to invalidate rendering in other Views
+ val scale = MutableStateFlow(1f)
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
new file mode 100644
index 0000000..40f9b28
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.graphics.Bitmap
+import android.graphics.Rect
+import androidx.annotation.ColorInt
+
+sealed class TaskThumbnailUiState {
+ data object Uninitialized : TaskThumbnailUiState()
+ data object LiveTile : TaskThumbnailUiState()
+ data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState()
+ data class Snapshot(
+ val bitmap: Bitmap,
+ val drawnRect: Rect,
+ @ColorInt val backgroundColor: Int
+ ) : TaskThumbnailUiState()
+}
+
+data class TaskThumbnail(val taskId: Int, val isRunning: Boolean)
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
new file mode 100644
index 0000000..2836c89
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Outline
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewOutlineProvider
+import androidx.annotation.ColorInt
+import com.android.launcher3.Utilities
+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.Uninitialized
+import com.android.quickstep.util.TaskCornerRadius
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import com.android.systemui.shared.system.QuickStepContract
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.launch
+
+class TaskThumbnailView : View {
+ // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
+ // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView]
+ // This is using a lazy for now because the dependencies cannot be obtained without DI.
+ val viewModel by lazy {
+ val recentsView =
+ RecentsViewContainer.containerFromContext<RecentsViewContainer>(context)
+ .getOverviewPanel<RecentsView<*, *>>()
+ TaskThumbnailViewModel(
+ recentsView.mRecentsViewData,
+ (parent as TaskView).taskViewData,
+ recentsView.mTasksRepository,
+ )
+ }
+
+ private var uiState: TaskThumbnailUiState = Uninitialized
+ private var inheritedScale: Float = 1f
+
+ private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val _measuredBounds = Rect()
+ private val measuredBounds: Rect
+ get() {
+ _measuredBounds.set(0, 0, measuredWidth, measuredHeight)
+ return _measuredBounds
+ }
+ private var cornerRadius: Float = TaskCornerRadius.get(context)
+ private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context)
+
+ constructor(context: Context?) : super(context)
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ ) : super(context, attrs, defStyleAttr)
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ // TODO(b/335396935) replace MainScope with shorter lifecycle.
+ MainScope().launch {
+ viewModel.uiState.collect { viewModelUiState ->
+ uiState = viewModelUiState
+ invalidate()
+ }
+ }
+ MainScope().launch { viewModel.recentsFullscreenProgress.collect { invalidateOutline() } }
+ MainScope().launch {
+ viewModel.inheritedScale.collect { viewModelInheritedScale ->
+ inheritedScale = viewModelInheritedScale
+ invalidateOutline()
+ }
+ }
+
+ clipToOutline = true
+ outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(measuredBounds, getCurrentCornerRadius())
+ }
+ }
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ when (val uiStateVal = uiState) {
+ is Uninitialized -> drawBackgroundOnly(canvas, Color.BLACK)
+ is LiveTile -> drawTransparentUiState(canvas)
+ is Snapshot -> drawSnapshotState(canvas, uiStateVal)
+ is BackgroundOnly -> drawBackgroundOnly(canvas, uiStateVal.backgroundColor)
+ }
+ }
+
+ private fun drawBackgroundOnly(canvas: Canvas, @ColorInt backgroundColor: Int) {
+ backgroundPaint.color = backgroundColor
+ canvas.drawRect(measuredBounds, backgroundPaint)
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration?) {
+ super.onConfigurationChanged(newConfig)
+
+ cornerRadius = TaskCornerRadius.get(context)
+ fullscreenCornerRadius = QuickStepContract.getWindowCornerRadius(context)
+ invalidateOutline()
+ }
+
+ private fun drawTransparentUiState(canvas: Canvas) {
+ canvas.drawRect(measuredBounds, CLEAR_PAINT)
+ }
+
+ private fun drawSnapshotState(canvas: Canvas, snapshot: Snapshot) {
+ drawBackgroundOnly(canvas, snapshot.backgroundColor)
+ canvas.drawBitmap(snapshot.bitmap, snapshot.drawnRect, measuredBounds, null)
+ }
+
+ private fun getCurrentCornerRadius() =
+ Utilities.mapRange(
+ viewModel.recentsFullscreenProgress.value,
+ cornerRadius,
+ fullscreenCornerRadius
+ ) / inheritedScale
+
+ companion object {
+ private val CLEAR_PAINT =
+ Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
new file mode 100644
index 0000000..4511ea7
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.annotation.ColorInt
+import android.graphics.Rect
+import androidx.core.graphics.ColorUtils
+import com.android.quickstep.recents.data.RecentTasksRepository
+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.Uninitialized
+import com.android.quickstep.task.viewmodel.TaskViewData
+import com.android.systemui.shared.recents.model.Task
+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.map
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class TaskThumbnailViewModel(
+ recentsViewData: RecentsViewData,
+ taskViewData: TaskViewData,
+ private val tasksRepository: RecentTasksRepository,
+) {
+ private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
+ private var boundTaskIsRunning = false
+
+ val recentsFullscreenProgress = recentsViewData.fullscreenProgress
+ val inheritedScale =
+ combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale ->
+ recentsScale * taskScale
+ }
+ val uiState: Flow<TaskThumbnailUiState> =
+ task
+ .flatMapLatest { taskFlow ->
+ taskFlow.map { taskVal ->
+ when {
+ taskVal == null -> Uninitialized
+ boundTaskIsRunning -> LiveTile
+ isBackgroundOnly(taskVal) ->
+ BackgroundOnly(taskVal.colorBackground.removeAlpha())
+ isSnapshotState(taskVal) -> {
+ val bitmap = taskVal.thumbnail?.thumbnail!!
+ Snapshot(
+ bitmap,
+ Rect(0, 0, bitmap.width, bitmap.height),
+ taskVal.colorBackground.removeAlpha()
+ )
+ }
+ else -> Uninitialized
+ }
+ }
+ }
+ .distinctUntilChanged()
+
+ fun bind(taskThumbnail: TaskThumbnail) {
+ boundTaskIsRunning = taskThumbnail.isRunning
+ task.value = tasksRepository.getTaskDataById(taskThumbnail.taskId)
+ }
+
+ private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
+
+ private fun isSnapshotState(task: Task): Boolean {
+ val thumbnailPresent = task.thumbnail?.thumbnail != null
+ val taskLocked = task.isLocked
+
+ return thumbnailPresent && !taskLocked
+ }
+
+ @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskThumbnailDataSource.kt b/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskThumbnailDataSource.kt
new file mode 100644
index 0000000..55598f0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskThumbnailDataSource.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail.data
+
+import com.android.launcher3.util.CancellableTask
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import java.util.function.Consumer
+
+interface TaskThumbnailDataSource {
+ fun updateThumbnailInBackground(
+ task: Task,
+ callback: Consumer<ThumbnailData>
+ ): CancellableTask<ThumbnailData>?
+}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
new file mode 100644
index 0000000..a8b5112
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.viewmodel
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class TaskViewData {
+ // This is typically a View concern but it is used to invalidate rendering in other Views
+ val scale = MutableStateFlow(1f)
+}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index e3772bd..cfa6b98 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -277,7 +277,8 @@
errorDetected |= printErrorIfTrue(
true,
prefix,
- /* errorMessage= */ "new gesture attempted while a requested recents"
+ /* errorMessage= */ (eventEntry.getDuplicateCount() + 1)
+ + " gesture(s) attempted while a requested recents"
+ " animation is still pending.",
writer);
break;
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 1e05a69..c54862a 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -216,6 +216,10 @@
return gestureEvent;
}
+ public int getDuplicateCount() {
+ return duplicateCount;
+ }
+
private void update(
@NonNull CompoundString compoundString,
ActiveGestureErrorDetector.GestureEvent gestureEvent) {
diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
index aeec36f..5efbb40 100644
--- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
+++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
@@ -57,16 +57,16 @@
* Registers the activity-created listener. If the activity is already created, then the
* callback provided in the constructor will be called synchronously.
*/
- public void register() {
+ public void register(String reasonString) {
mIsRegistered = true;
- mActivityTracker.registerCallback(this);
+ mActivityTracker.registerCallback(this, reasonString);
}
/**
* After calling this, we won't {@link #init} even when the activity is ready.
*/
- public void unregister() {
- mActivityTracker.unregisterCallback(this);
+ public void unregister(String reasonString) {
+ mActivityTracker.unregisterCallback(this, reasonString);
mIsRegistered = false;
mOnInitListener = null;
}
diff --git a/quickstep/src/com/android/quickstep/util/AnimUtils.java b/quickstep/src/com/android/quickstep/util/AnimUtils.java
index 1f2a02c..8e3d44f 100644
--- a/quickstep/src/com/android/quickstep/util/AnimUtils.java
+++ b/quickstep/src/com/android/quickstep/util/AnimUtils.java
@@ -16,10 +16,12 @@
package com.android.quickstep.util;
+import static com.android.app.animation.Interpolators.clampToProgress;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.os.Bundle;
import android.os.IRemoteCallback;
+import android.view.animation.Interpolator;
import com.android.launcher3.util.RunnableList;
@@ -67,4 +69,15 @@
}
};
}
+
+ /**
+ * Returns a function that runs the given interpolator such that the entire progress is set
+ * between the given duration. That is, we set the interpolation to 0 until startDelay and reach
+ * 1 by (startDelay + duration).
+ */
+ public static Interpolator clampToDuration(Interpolator interpolator, float startDelay,
+ float duration, float totalDuration) {
+ return clampToProgress(interpolator, startDelay / totalDuration,
+ (startDelay + duration) / totalDuration);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index 16f2065..c7c04ed 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -18,7 +18,6 @@
import static com.android.app.animation.Interpolators.DECELERATE;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
-import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
@@ -35,7 +34,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -44,8 +42,10 @@
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AllAppsSwipeController;
+import com.android.quickstep.DeviceConfigWrapper;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
/**
* Controls an animation that can go beyond progress = 1, at which point resistance should be
@@ -159,11 +159,13 @@
PendingAnimation resistAnim = createRecentsResistanceAnim(params);
// Apply All Apps animation during the resistance animation.
- if (recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()) {
- StatefulActivity activity =
- recentsOrientedState.getActivityInterface().getCreatedActivity();
- if (activity != null) {
- StateManager<LauncherState> stateManager = activity.getStateManager();
+ if (recentsOrientedState.getContainerInterface().allowAllAppsFromOverview()) {
+ RecentsViewContainer container =
+ recentsOrientedState.getContainerInterface().getCreatedContainer();
+ if (container != null) {
+ RecentsView recentsView = container.getOverviewPanel();
+ StateManager<LauncherState, StatefulActivity<LauncherState>> stateManager =
+ recentsView.getStateManager();
if (stateManager.isInStableState(LauncherState.BACKGROUND_APP)
&& stateManager.isInTransition()) {
@@ -186,12 +188,12 @@
private static float getAllAppsThreshold(Context context,
RecentsOrientedState recentsOrientedState, DeviceProfile dp) {
int transitionDragLength =
- recentsOrientedState.getActivityInterface().getSwipeUpDestinationAndLength(
+ recentsOrientedState.getContainerInterface().getSwipeUpDestinationAndLength(
dp, context, TEMP_RECT,
recentsOrientedState.getOrientationHandler());
float dragLengthFactor = (float) dp.heightPx / transitionDragLength;
// -1s are because 0-1 is reserved for the normal transition.
- float threshold = LauncherPrefs.get(context).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;
+ float threshold = DeviceConfigWrapper.get().getAllAppsOverviewThreshold() / 100f;
return (threshold - 1) / (dragLengthFactor - 1);
}
@@ -204,7 +206,7 @@
Rect startRect = new Rect();
RecentsPagedOrientationHandler orientationHandler = params.recentsOrientedState
.getOrientationHandler();
- params.recentsOrientedState.getActivityInterface()
+ params.recentsOrientedState.getContainerInterface()
.calculateTaskSize(params.context, params.dp, startRect, orientationHandler);
long distanceToCover = startRect.bottom;
PendingAnimation resistAnim = params.resistAnim != null
@@ -304,14 +306,14 @@
this.translationProperty = translationProperty;
if (dp.isTablet) {
resistanceParams =
- recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
+ recentsOrientedState.getContainerInterface().allowAllAppsFromOverview()
? RecentsResistanceParams.FROM_APP_TO_ALL_APPS_TABLET
: enableGridOnlyOverview()
? RecentsResistanceParams.FROM_APP_TABLET_GRID_ONLY
: RecentsResistanceParams.FROM_APP_TABLET;
} else {
resistanceParams =
- recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
+ recentsOrientedState.getContainerInterface().allowAllAppsFromOverview()
? RecentsResistanceParams.FROM_APP_TO_ALL_APPS
: RecentsResistanceParams.FROM_APP;
}
diff --git a/quickstep/src/com/android/quickstep/util/AppCloseConfig.java b/quickstep/src/com/android/quickstep/util/AppCloseConfig.java
deleted file mode 100644
index bec3379..0000000
--- a/quickstep/src/com/android/quickstep/util/AppCloseConfig.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2021 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.annotation.FloatRange;
-import android.annotation.IntRange;
-
-/*
- * Adds getter methods to {@link MultiValueUpdateListener} specific to app close animation,
- * so that the entire animation can be defined in one place.
- */
-public abstract class AppCloseConfig extends MultiValueUpdateListener {
-
- /**
- * Returns the translation y of the workspace contents.
- */
- public abstract float getWorkspaceTransY();
-
- /*
- * Returns the scale of the workspace contents.
- */
- public abstract float getWorkspaceScale();
-
- /*
- * Returns the alpha of the window.
- */
- public abstract @FloatRange(from = 0, to = 1) float getWindowAlpha();
-
- /*
- * Returns the alpha of the foreground layer of an adaptive icon.
- */
- public abstract @IntRange(from = 0, to = 255) int getFgAlpha();
-
- /*
- * Returns the corner radius of the window and icon.
- */
- public abstract float getCornerRadius();
-
- /*
- * Returns the interpolated progress of the animation.
- */
- public abstract float getInterpolatedProgress();
-
-}
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 757f1f8..6f9cbfd 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -19,49 +19,59 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
import static com.android.launcher3.model.data.AppInfo.PACKAGE_KEY_COMPARATOR;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SUPPORTS_MULTI_INSTANCE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_NONE;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.isPersistentSnapPosition;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.Launcher;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.apppairs.AppPairIcon;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskUtils;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import java.util.Arrays;
@@ -98,6 +108,55 @@
}
/**
+ * Returns whether the specified GroupedTaskView can be saved as an app pair.
+ */
+ public boolean canSaveAppPair(TaskView taskView) {
+ if (mContext == null) {
+ // Can ignore as the activity is already destroyed
+ return false;
+ }
+
+ // Disallow saving app pairs if:
+ // - app pairs feature is not enabled
+ // - the task in question is a single task
+ // - at least one app in app pair is unpinnable
+ // - the task is not a GroupedTaskView
+ // - both tasks in the GroupedTaskView are from the same app and the app does not
+ // support multi-instance
+ boolean hasUnpinnableApp = taskView.getTaskContainers().stream()
+ .anyMatch(att -> att != null && att.getItemInfo() != null
+ && ((att.getItemInfo().runtimeStatusFlags
+ & ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0));
+ if (!FeatureFlags.enableAppPairs()
+ || !taskView.containsMultipleTasks()
+ || hasUnpinnableApp
+ || !(taskView instanceof GroupedTaskView)) {
+ return false;
+ }
+
+ GroupedTaskView gtv = (GroupedTaskView) taskView;
+ List<TaskView.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);
+
+ if (app1 == null || app2 == 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()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Creates a new app pair ItemInfo and adds it to the workspace.
* <br>
* We create WorkspaceItemInfos to save onto the app pair in the following way:
@@ -112,63 +171,56 @@
* well on trampoline apps).
*/
public void saveAppPair(GroupedTaskView gtv) {
- TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers();
- WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo();
- WorkspaceItemInfo recentsInfo2 = attributes[1].getItemInfo();
- WorkspaceItemInfo app1 = lookupLaunchableItem(recentsInfo1.getComponentKey());
- WorkspaceItemInfo app2 = lookupLaunchableItem(recentsInfo2.getComponentKey());
+ InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
+ List<TaskView.TaskContainer> containers = gtv.getTaskContainers();
+ WorkspaceItemInfo recentsInfo1 = containers.get(0).getItemInfo();
+ WorkspaceItemInfo recentsInfo2 = containers.get(1).getItemInfo();
+ WorkspaceItemInfo app1 = resolveAppPairWorkspaceInfo(recentsInfo1);
+ WorkspaceItemInfo app2 = resolveAppPairWorkspaceInfo(recentsInfo2);
- // If app lookup fails, use the WorkspaceItemInfo that we have, but try to override default
- // intent with one from PackageManager.
- if (app1 == null) {
- Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo1.title
- + " failed. Falling back to the WorkspaceItemInfo from Recents.");
- app1 = convertRecentsItemToAppItem(recentsInfo1);
+ if (app1 == null || app2 == null) {
+ // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
+ // not create the app pair if the workspace items can't be resolved
+ Log.w(TAG, "Failed to save app pair due to invalid apps ("
+ + "app1=" + recentsInfo1.getComponentKey().componentName
+ + " app2=" + recentsInfo2.getComponentKey().componentName + ")");
+ return;
}
- if (app2 == null) {
- Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo2.title
- + " failed. Falling back to the WorkspaceItemInfo from Recents.");
- app2 = convertRecentsItemToAppItem(recentsInfo2);
- }
-
- // WorkspaceItemProcessor won't process these new ItemInfos until the next launcher restart,
- // so update some flags now.
- updateWorkspaceItemFlags(app1);
- updateWorkspaceItemFlags(app2);
@PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
+ if (snapPosition == SNAP_TO_NONE) {
+ // Free snap mode is enabled, just save it as 50/50 split.
+ snapPosition = SNAP_TO_50_50;
+ }
if (!isPersistentSnapPosition(snapPosition)) {
- // if we received an illegal snap position, log an error and do not create the app pair.
- Log.wtf(TAG, "tried to save an app pair with illegal snapPosition " + snapPosition);
+ // If we received an illegal snap position, log an error and do not create the app pair
+ Log.wtf(TAG, "Tried to save an app pair with illegal snapPosition "
+ + snapPosition);
return;
}
app1.rank = encodeRank(SPLIT_POSITION_TOP_OR_LEFT, snapPosition);
app2.rank = encodeRank(SPLIT_POSITION_BOTTOM_OR_RIGHT, snapPosition);
- FolderInfo newAppPair = FolderInfo.createAppPair(app1, app2);
-
- if (newAppPair.contents.size() != 2) {
- // if app pair doesn't have exactly 2 members, log an error and do not create the app
- // pair.
- Log.wtf(TAG,
- "tried to save an app pair with " + newAppPair.contents.size() + " members");
- return;
- }
+ AppPairInfo newAppPair = new AppPairInfo(app1, app2);
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
MODEL_EXECUTOR.execute(() -> {
- newAppPair.contents.forEach(member -> {
+ newAppPair.getAppContents().forEach(member -> {
member.title = "";
member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
iconCache.getTitleAndIcon(member, member.usingLowResIcon());
});
- newAppPair.title = getDefaultTitle(newAppPair.contents.get(0).title,
- newAppPair.contents.get(1).title);
MAIN_EXECUTOR.execute(() -> {
LauncherAccessibilityDelegate delegate =
- Launcher.getLauncher(mContext).getAccessibilityDelegate();
+ QuickstepLauncher.getLauncher(mContext).getAccessibilityDelegate();
if (delegate != null) {
- delegate.addToWorkspace(newAppPair, true);
+ delegate.addToWorkspace(newAppPair, true, (success) -> {
+ if (success) {
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
+ } else {
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
+ }
+ });
mStatsLogManager.logger().withItemInfo(newAppPair)
.log(StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_SAVE);
}
@@ -179,12 +231,18 @@
/**
* Launches an app pair by searching the RecentsModel for running instances of each app, and
* staging either those running instances or launching the apps as new Intents.
+ *
+ * @param cuj Should be an integer from {@link Cuj} or -1 if no CUJ needs to be logged for jank
+ * monitoring
*/
- public void launchAppPair(AppPairIcon appPairIcon) {
- WorkspaceItemInfo app1 = appPairIcon.getInfo().contents.get(0);
- WorkspaceItemInfo app2 = appPairIcon.getInfo().contents.get(1);
+ public void launchAppPair(AppPairIcon appPairIcon, int cuj) {
+ WorkspaceItemInfo app1 = appPairIcon.getInfo().getFirstApp();
+ WorkspaceItemInfo app2 = appPairIcon.getInfo().getSecondApp();
ComponentKey app1Key = new ComponentKey(app1.getTargetComponent(), app1.user);
ComponentKey app2Key = new ComponentKey(app2.getTargetComponent(), app2.user);
+ mSplitSelectStateController.setLaunchingCuj(cuj);
+ InteractionJankMonitorWrapper.begin(appPairIcon, cuj);
+
mSplitSelectStateController.findLastActiveTasksAndRunCallback(
Arrays.asList(app1Key, app2Key),
false /* findExactPairMatch */,
@@ -223,68 +281,38 @@
}
/**
+ * Returns an AppInfo associated with the app for the given ComponentKey, or null if no such
+ * package exists in the AllAppsStore.
+ */
+ @Nullable
+ private AppInfo resolveAppInfoByComponent(@NonNull ComponentKey key) {
+ AllAppsStore appsStore = ActivityContext.lookupContext(mContext)
+ .getAppsView().getAppsStore();
+
+ // First look up the app info in order of:
+ // - The exact activity for the recent task
+ // - The first(?) loaded activity from the package
+ AppInfo appInfo = appsStore.getApp(key);
+ if (appInfo == null) {
+ appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR);
+ }
+ return appInfo;
+ }
+
+ /**
* Creates a new launchable WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION by looking the
* ComponentKey up in the AllAppsStore. If no app is found, attempts a lookup by package
* instead. If that lookup fails, returns null.
*/
@Nullable
- private WorkspaceItemInfo lookupLaunchableItem(@Nullable ComponentKey key) {
- if (key == null) {
+ private WorkspaceItemInfo resolveAppPairWorkspaceInfo(
+ @NonNull WorkspaceItemInfo recentTaskInfo) {
+ // ComponentKey should never be null (see TaskView#getItemInfo)
+ AppInfo appInfo = resolveAppInfoByComponent(recentTaskInfo.getComponentKey());
+ if (appInfo == null) {
return null;
}
-
- AllAppsStore appsStore = Launcher.getLauncher(mContext).getAppsView().getAppsStore();
-
- // Lookup by ComponentKey
- AppInfo appInfo = appsStore.getApp(key);
- if (appInfo == null) {
- // Lookup by package
- appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR);
- }
-
- return appInfo != null ? appInfo.makeWorkspaceItem(mContext) : null;
- }
-
- /**
- * Updates flags for newly created WorkspaceItemInfos.
- */
- private void updateWorkspaceItemFlags(WorkspaceItemInfo wii) {
- PackageManager pm = mContext.getPackageManager();
- ActivityInfo ai = null;
- try {
- ai = pm.getActivityInfo(wii.getTargetComponent(), 0);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "PackageManager lookup failed.");
- }
-
- if (ai != null) {
- wii.status = ai.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE
- ? wii.status | WorkspaceItemInfo.FLAG_NON_RESIZEABLE
- : wii.status & ~WorkspaceItemInfo.FLAG_NON_RESIZEABLE;
- }
- }
-
- /**
- * Converts a WorkspaceItemInfo of itemType=ITEM_TYPE_TASK (from a Recents task) to a new
- * WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION.
- */
- private WorkspaceItemInfo convertRecentsItemToAppItem(WorkspaceItemInfo recentsItem) {
- if (recentsItem.itemType != LauncherSettings.Favorites.ITEM_TYPE_TASK) {
- Log.w(TAG, "Expected ItemInfo of type ITEM_TYPE_TASK, but received "
- + recentsItem.itemType);
- }
-
- WorkspaceItemInfo launchableItem = recentsItem.clone();
- PackageManager p = mContext.getPackageManager();
- Intent launchIntent = p.getLaunchIntentForPackage(recentsItem.getTargetPackage());
- Log.w(TAG, "Initial intent from Recents: " + launchableItem.intent + "\n"
- + "Intent from PackageManager: " + launchIntent);
- if (launchIntent != null) {
- // If lookup from PackageManager fails, just use the existing intent
- launchableItem.intent = launchIntent;
- }
- launchableItem.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- return launchableItem;
+ return appInfo.makeWorkspaceItem(mContext);
}
/**
@@ -311,7 +339,7 @@
itemInfos.stream().map(ItemInfo::getComponentKey).toList();
// Use TopTaskTracker to find the currently running app (or apps)
- TopTaskTracker topTaskTracker = getTopTaskTracker(context);
+ TopTaskTracker topTaskTracker = getTopTaskTracker();
// getRunningSplitTasksIds() will return a pair of ids if we are currently running a
// split pair, or an empty array with zero length if we are running a single app.
@@ -343,7 +371,8 @@
&& !lastActiveTasksOfAppPair.contains(runningTaskId2)) {
// Neither A nor B are on screen, so just launch a new app pair
// normally.
- launchAppPair(launchingIconView);
+ launchAppPair(launchingIconView,
+ CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR);
} else {
// Exactly one app (A or B) is on-screen, so we have to launch the other
// on the appropriate side.
@@ -388,7 +417,8 @@
if (!task1IsOnScreen && !task2IsOnScreen) {
// Neither App A nor App B are on-screen, launch the app pair normally.
- launchAppPair(launchingIconView);
+ launchAppPair(launchingIconView,
+ CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR);
} else {
// Either A or B is on-screen, so launch the other on the appropriate
// side.
@@ -479,17 +509,10 @@
}
/**
- * Returns a formatted default title for the app pair.
- */
- public String getDefaultTitle(CharSequence app1, CharSequence app2) {
- return mContext.getString(R.string.app_pair_default_title, app1, app2);
- }
-
- /**
* Gets the TopTaskTracker, which is a cached record of the top running Task.
*/
@VisibleForTesting
- public TopTaskTracker getTopTaskTracker(Context context) {
- return TopTaskTracker.INSTANCE.get(context);
+ public TopTaskTracker getTopTaskTracker() {
+ return TopTaskTracker.INSTANCE.get(mContext);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index 660fc22..7acb28d 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -20,48 +20,74 @@
import com.android.launcher3.R;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.SafeCloseable;
import java.io.PrintWriter;
import java.util.Optional;
/** Class to manage Assistant states. */
-public class AssistStateManager implements ResourceBasedOverride {
+public class AssistStateManager implements ResourceBasedOverride, SafeCloseable {
public static final MainThreadInitializedObject<AssistStateManager> INSTANCE =
forOverride(AssistStateManager.class, R.string.assist_state_manager_class);
public AssistStateManager() {}
- /** Whether search supports haptic on invocation. */
- public boolean supportsCommitHaptic() {
+ /** Return {@code true} if the Settings toggle is enabled. */
+ public boolean isSettingsAllEntrypointsEnabled() {
return false;
}
- /** Whether search is available. */
- public boolean isSearchAvailable() {
+ /** Whether search supports showing on the lockscreen. */
+ public boolean supportsShowWhenLocked() {
return false;
}
- /** Whether CsHelper CtS invocation path is available. */
- public Optional<Boolean> isCsHelperAvailable() {
+ /** Whether ContextualSearchService invocation path is available. */
+ public boolean isContextualSearchServiceAvailable() {
+ return false;
+ }
+
+ /** Get the Launcher overridden long press nav handle duration to trigger Assistant. */
+ public Optional<Long> getLPNHDurationMillis() {
return Optional.empty();
}
- /** Whether VIS CtS invocation path is available. */
- public Optional<Boolean> isVisAvailable() {
+ /**
+ * Get the Launcher overridden long press nav handle touch slop multiplier to trigger Assistant.
+ */
+ public Optional<Float> getLPNHCustomSlopMultiplier() {
return Optional.empty();
}
- /** Return {@code true} if the Settings toggle is enabled. */
- public boolean isSettingsNavHandleEnabled() {
- return false;
+ /** Get the Launcher overridden long press home duration to trigger Assistant. */
+ public Optional<Long> getLPHDurationMillis() {
+ return Optional.empty();
}
- /** Return {@code true} if the Settings toggle is enabled. */
- public boolean isSettingsHomeButtonEnabled() {
- return false;
+ /** Get the Launcher overridden long press home touch slop multiplier to trigger Assistant. */
+ public Optional<Float> getLPHCustomSlopMultiplier() {
+ return Optional.empty();
+ }
+
+ /** Get the long press duration data source. */
+ public int getDurationDataSource() {
+ return 0;
+ }
+
+ /** Get the long press touch slop multiplier data source. */
+ public int getSlopDataSource() {
+ return 0;
+ }
+
+ /** Get the haptic bit overridden by AGSA. */
+ public Optional<Boolean> getShouldPlayHapticOverride() {
+ return Optional.empty();
}
/** Dump states. */
public void dump(String prefix, PrintWriter writer) {}
+
+ @Override
+ public void close() {}
}
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
index 99f564c..5d6bb1d 100644
--- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+
import android.app.WallpaperManager;
import android.os.IBinder;
import android.util.FloatProperty;
@@ -33,6 +35,9 @@
* Utility class for applying depth effect
*/
public class BaseDepthController {
+ public static final float DEPTH_0_PERCENT = 0f;
+ public static final float DEPTH_60_PERCENT = 0.6f;
+ public static final float DEPTH_70_PERCENT = 0.7f;
private static final FloatProperty<BaseDepthController> DEPTH =
new FloatProperty<BaseDepthController>("depth") {
@@ -127,10 +132,14 @@
float depth = mDepth;
IBinder windowToken = mLauncher.getRootView().getWindowToken();
if (windowToken != null) {
- // The API's full zoom-out is three times larger than the zoom-out we apply to the
- // icons. To keep the two consistent throughout the animation while keeping Launcher's
- // concept of full depth unchanged, we divide the depth by 3 here.
- mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3);
+ if (enableScalingRevealHomeAnimation()) {
+ mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
+ } else {
+ // The API's full zoom-out is three times larger than the zoom-out we apply to the
+ // icons. To keep the two consistent throughout the animation while keeping
+ // Launcher's concept of full depth unchanged, we divide the depth by 3 here.
+ mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3);
+ }
}
if (!BlurUtils.supportsBlursOnWindows()) {
@@ -150,8 +159,15 @@
boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg && !mPauseBlurs;
+ float blurAmount;
+ if (enableScalingRevealHomeAnimation()) {
+ blurAmount = mapDepthToBlur(depth);
+ } else {
+ blurAmount = depth;
+ }
mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg || mPauseBlurs
- ? 0 : (int) (depth * mMaxBlurRadius);
+ ? 0 : (int) (blurAmount * mMaxBlurRadius);
+
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
.setBackgroundBlurRadius(mSurface, mCurrentBlur)
.setOpaque(mSurface, isSurfaceOpaque);
@@ -197,4 +213,12 @@
applyDepthAndBlur();
}
}
+
+ /**
+ * Maps depth values to blur amounts as a percentage of the max blur.
+ * The blur percentage grows linearly with depth, and maxes out at 30% depth.
+ */
+ private static float mapDepthToBlur(float depth) {
+ return Math.min(3 * depth, 1f);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
index 328a727..f1e2dd7 100644
--- a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
@@ -21,8 +21,11 @@
import android.view.ViewGroup;
import android.view.WindowManager;
+import androidx.annotation.MainThread;
+
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.dagger.UnfoldMain;
import com.android.systemui.unfold.updates.RotationChangeProvider;
import java.util.HashMap;
@@ -34,7 +37,7 @@
public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProgressListener {
private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimation;
- private final RotationChangeProvider mRotationChangeProvider;
+ @UnfoldMain private final RotationChangeProvider mRotationChangeProvider;
private final Map<ViewGroup, Boolean> mOriginalClipToPadding = new HashMap<>();
private final Map<ViewGroup, Boolean> mOriginalClipChildren = new HashMap<>();
@@ -48,7 +51,7 @@
private Float mLastTransitionProgress = null;
public BaseUnfoldMoveFromCenterAnimator(WindowManager windowManager,
- RotationChangeProvider rotationChangeProvider) {
+ @UnfoldMain RotationChangeProvider rotationChangeProvider) {
mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
new LauncherViewsMoveFromCenterTranslationApplier());
mRotationChangeProvider = rotationChangeProvider;
@@ -143,8 +146,14 @@
private class UnfoldMoveFromCenterRotationListener implements
RotationChangeProvider.RotationListener {
+ @MainThread
@Override
public void onRotationChanged(@Rotation int newRotation) {
+ onRotationChangedInternal(newRotation);
+ }
+
+ @MainThread
+ private void onRotationChangedInternal(@Rotation int newRotation) {
mMoveFromCenterAnimation.updateDisplayProperties(newRotation);
updateRegisteredViewsIfNeeded();
}
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
index 44eb070..7e51fcf 100644
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
@@ -50,7 +50,7 @@
private val disappearanceDurationMs: Long,
private val interpolator: Interpolator,
) {
- private val borderAnimationProgress = AnimatedFloat { updateOutline() }
+ private val borderAnimationProgress = AnimatedFloat { _ -> updateOutline() }
private val borderPaint =
Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = borderColor
@@ -86,7 +86,7 @@
fun createSimpleBorderAnimator(
@Px borderRadiusPx: Int,
@Px borderWidthPx: Int,
- boundsBuilder: (rect: Rect?) -> Unit,
+ boundsBuilder: (Rect) -> Unit,
targetView: View,
@ColorInt borderColor: Int = DEFAULT_BORDER_COLOR,
appearanceDurationMs: Long = DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
@@ -224,6 +224,7 @@
val borderWidth: Float
get() = borderWidthPx * animationProgress
+
val alignmentAdjustment: Float
// Outset the border by half the width to create an outwards-growth animation
get() = -borderWidth / 2f + alignmentAdjustmentInset
@@ -250,7 +251,7 @@
/** BorderAnimationParams that simply draws the border outside the bounds of the target view. */
private class SimpleParams(
@Px borderWidthPx: Int,
- boundsBuilder: (rect: Rect?) -> Unit,
+ boundsBuilder: (Rect) -> Unit,
targetView: View,
) : BorderAnimationParams(borderWidthPx, boundsBuilder, targetView) {
override val alignmentAdjustmentInset = 0
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
index b3f5d82..8d99069 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.java
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -21,7 +21,7 @@
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
-import java.util.ArrayList;
+import java.util.List;
/**
* A {@link Task} container that can contain N number of tasks that are part of the desktop in
@@ -30,9 +30,9 @@
public class DesktopTask extends GroupTask {
@NonNull
- public final ArrayList<Task> tasks;
+ public final List<Task> tasks;
- public DesktopTask(@NonNull ArrayList<Task> tasks) {
+ public DesktopTask(@NonNull List<Task> tasks) {
super(tasks.get(0), null, null, TaskView.Type.DESKTOP);
this.tasks = tasks;
}
@@ -53,7 +53,19 @@
}
@Override
+ @NonNull
+ public List<Task> getTasks() {
+ return tasks;
+ }
+
+ @Override
public DesktopTask copy() {
return new DesktopTask(tasks);
}
+
+ @Override
+ public String toString() {
+ return "type=" + taskViewType + " tasks=" + tasks;
+ }
+
}
diff --git a/quickstep/src/com/android/quickstep/util/DeviceConfigHelper.kt b/quickstep/src/com/android/quickstep/util/DeviceConfigHelper.kt
new file mode 100644
index 0000000..d36dc7e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/DeviceConfigHelper.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.app.ActivityThread
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.OnPropertiesChangedListener
+import android.provider.DeviceConfig.Properties
+import androidx.annotation.WorkerThread
+import com.android.launcher3.BuildConfig
+import com.android.launcher3.util.Executors
+
+/** Utility class to manage a set of device configurations */
+class DeviceConfigHelper<ConfigType>(private val factory: (PropReader) -> ConfigType) {
+
+ var config: ConfigType
+ private set
+ private val allKeys: Set<String>
+ private val propertiesListener = OnPropertiesChangedListener { onDevicePropsChanges(it) }
+ private val sharedPrefChangeListener = OnSharedPreferenceChangeListener { _, _ ->
+ recreateConfig()
+ }
+
+ private val changeListeners = mutableListOf<Runnable>()
+
+ init {
+ // Initialize the default config once.
+ allKeys = HashSet()
+ config =
+ factory(
+ PropReader(
+ object : PropProvider {
+ override fun <T : Any> get(key: String, fallback: T): T {
+ if (fallback is Int) {
+ allKeys.add(key)
+ return DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, fallback) as T
+ } else if (fallback is Boolean) {
+ allKeys.add(key)
+ return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, fallback)
+ as T
+ } else return fallback
+ }
+ }
+ )
+ )
+
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_LAUNCHER,
+ Executors.UI_HELPER_EXECUTOR,
+ propertiesListener
+ )
+ if (BuildConfig.IS_DEBUG_DEVICE) {
+ prefs.registerOnSharedPreferenceChangeListener(sharedPrefChangeListener)
+ }
+ }
+
+ @WorkerThread
+ private fun onDevicePropsChanges(properties: Properties) {
+ if (NAMESPACE_LAUNCHER != properties.namespace) return
+ if (!allKeys.any(properties.keyset::contains)) return
+ recreateConfig()
+ }
+
+ private fun recreateConfig() {
+ val myProps =
+ DeviceConfig.getProperties(NAMESPACE_LAUNCHER, *allKeys.toTypedArray<String>())
+ config =
+ factory(
+ PropReader(
+ object : PropProvider {
+ override fun <T : Any> get(key: String, fallback: T): T {
+ if (fallback is Int) return myProps.getInt(key, fallback) as T
+ else if (fallback is Boolean)
+ return myProps.getBoolean(key, fallback) as T
+ else return fallback
+ }
+ }
+ )
+ )
+ Executors.MAIN_EXECUTOR.execute { changeListeners.forEach(Runnable::run) }
+ }
+
+ /** Adds a listener for property changes */
+ fun addChangeListener(r: Runnable) = changeListeners.add(r)
+
+ /** Removes a previously added listener */
+ fun removeChangeListener(r: Runnable) = changeListeners.remove(r)
+
+ fun close() {
+ DeviceConfig.removeOnPropertiesChangedListener(propertiesListener)
+ if (BuildConfig.IS_DEBUG_DEVICE) {
+ prefs.unregisterOnSharedPreferenceChangeListener(sharedPrefChangeListener)
+ }
+ }
+
+ internal interface PropProvider {
+ fun <T : Any> get(key: String, fallback: T): T
+ }
+
+ /** The reader is sent to the config for initialization */
+ class PropReader internal constructor(private val f: PropProvider) {
+
+ @JvmOverloads
+ fun <T : Any> get(key: String, fallback: T, desc: String? = null): T {
+ val v = f.get(key, fallback)
+ if (BuildConfig.IS_DEBUG_DEVICE && desc != null) {
+ if (v is Int) {
+ allProps[key] = DebugInfo(key, desc, true, fallback)
+ return prefs.getInt(key, v) as T
+ } else if (v is Boolean) {
+ allProps[key] = DebugInfo(key, desc, false, fallback)
+ return prefs.getBoolean(key, v) as T
+ }
+ }
+ return v
+ }
+ }
+
+ class DebugInfo<T>(
+ val key: String,
+ val desc: String,
+ val isInt: Boolean,
+ val valueInCode: T,
+ )
+
+ companion object {
+ const val NAMESPACE_LAUNCHER = "launcher"
+
+ val allProps = mutableMapOf<String, DebugInfo<*>>()
+
+ private const val FLAGS_PREF_NAME = "featureFlags"
+
+ val prefs: SharedPreferences by lazy {
+ ActivityThread.currentApplication()
+ .createDeviceProtectedStorageContext()
+ .getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
index 32a15a2..e6e94e6 100644
--- a/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
+++ b/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
@@ -20,30 +20,15 @@
import android.os.RemoteException
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
-import android.window.IRemoteTransition
import android.window.IRemoteTransitionFinishedCallback
+import android.window.RemoteTransitionStub
import android.window.TransitionInfo
import com.android.launcher3.anim.AnimatorListeners.forEndCallback
import com.android.launcher3.util.Executors
import com.android.wm.shell.shared.TransitionUtil
/** Remote animation which fades out the closing targets */
-class FadeOutRemoteTransition : IRemoteTransition.Stub() {
-
- override fun mergeAnimation(
- iBinder: IBinder,
- transitionInfo: TransitionInfo,
- transaction: Transaction,
- mergeTarget: IBinder,
- finishCB: IRemoteTransitionFinishedCallback
- ) {
-
- try {
- finishCB.onTransitionFinished(null, Transaction())
- } catch (e: RemoteException) {
- // Ignore
- }
- }
+class FadeOutRemoteTransition : RemoteTransitionStub() {
override fun startAnimation(
transition: IBinder,
@@ -83,6 +68,4 @@
Executors.MAIN_EXECUTOR.execute { anim.start() }
}
-
- override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {}
}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index 9c49647..945ffe3 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -23,6 +23,10 @@
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
/**
* A {@link 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.
@@ -62,6 +66,17 @@
}
/**
+ * Returns a List of all the Tasks in this GroupTask
+ */
+ public List<Task> getTasks() {
+ if (task2 == null) {
+ return Collections.singletonList(task1);
+ } else {
+ return Arrays.asList(task1, task2);
+ }
+ }
+
+ /**
* Create a copy of this instance
*/
public GroupTask copy() {
@@ -70,4 +85,10 @@
task2 != null ? new Task(task2) : null,
mSplitBounds);
}
+
+ @Override
+ public String toString() {
+ return "type=" + taskViewType + " task1=" + task1 + " task2=" + task2;
+ }
+
}
diff --git a/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java b/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
index 8209c09..843619d 100644
--- a/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
+++ b/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
@@ -17,11 +17,11 @@
import androidx.annotation.UiThread;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
+import com.android.quickstep.views.RecentsViewContainer;
import java.util.function.Supplier;
@@ -31,13 +31,13 @@
*/
public class InputProxyHandlerFactory implements Supplier<InputConsumer> {
- private final BaseActivityInterface mActivityInterface;
+ private final BaseContainerInterface mContainerInterface;
private final GestureState mGestureState;
@UiThread
- public InputProxyHandlerFactory(BaseActivityInterface activityInterface,
+ public InputProxyHandlerFactory(BaseContainerInterface activityInterface,
GestureState gestureState) {
- mActivityInterface = activityInterface;
+ mContainerInterface = activityInterface;
mGestureState = gestureState;
}
@@ -46,8 +46,8 @@
*/
@Override
public InputConsumer get() {
- StatefulActivity activity = mActivityInterface.getCreatedActivity();
- return activity == null ? InputConsumer.NO_OP
- : new OverviewInputConsumer(mGestureState, activity, null, true);
+ RecentsViewContainer container = mContainerInterface.getCreatedContainer();
+ return container == null ? InputConsumer.NO_OP
+ : new OverviewInputConsumer(mGestureState, container, null, true);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index 61ba5ac..26668c8 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -30,15 +30,16 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Hotseat;
-import com.android.launcher3.Launcher;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.HorizontalInsettableView;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.unfold.LauncherJankMonitorTransitionProgressListener;
import com.android.quickstep.util.unfold.PreemptiveUnfoldTransitionProgressProvider;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.dagger.UnfoldMain;
import com.android.systemui.unfold.updates.RotationChangeProvider;
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
@@ -56,7 +57,7 @@
private static final FloatProperty<Hotseat> HOTSEAT_SCALE_PROPERTY =
HOTSEAT_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_UNFOLD_ANIMATION);
- private final Launcher mLauncher;
+ private final QuickstepLauncher mLauncher;
private final ScopedUnfoldTransitionProgressProvider mProgressProvider;
private final NaturalRotationUnfoldProgressProvider mNaturalOrientationProgressProvider;
private final UnfoldMoveFromCenterHotseatAnimator mUnfoldMoveFromCenterHotseatAnimator;
@@ -73,10 +74,10 @@
private HorizontalInsettableView mQsbInsettable;
public LauncherUnfoldAnimationController(
- Launcher launcher,
+ QuickstepLauncher launcher,
WindowManager windowManager,
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider,
- RotationChangeProvider rotationChangeProvider) {
+ @UnfoldMain RotationChangeProvider rotationChangeProvider) {
mLauncher = launcher;
if (FeatureFlags.PREEMPTIVE_UNFOLD_ANIMATION_START.get()) {
diff --git a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
index 1c3c9c2..72fc2a6 100644
--- a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
+++ b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
@@ -18,6 +18,8 @@
import android.animation.ValueAnimator;
import android.view.animation.Interpolator;
+import com.android.launcher3.Utilities;
+
import java.util.ArrayList;
/**
@@ -31,14 +33,11 @@
@Override
public final void onAnimationUpdate(ValueAnimator animator) {
final float percent = animator.getAnimatedFraction();
- final float currentPlayTime = percent * animator.getDuration();
for (int i = mAllProperties.size() - 1; i >= 0; i--) {
FloatProp prop = mAllProperties.get(i);
- float time = Math.max(0, currentPlayTime - prop.mDelay);
- float newPercent = Math.min(1f, time / prop.mDuration);
- newPercent = prop.mInterpolator.getInterpolation(newPercent);
- prop.value = prop.mEnd * newPercent + prop.mStart * (1 - newPercent);
+ float interpolatedPercent = prop.mInterpolator.getInterpolation(percent);
+ prop.value = Utilities.mapRange(interpolatedPercent, prop.mStart, prop.mEnd);
}
onUpdate(percent, false /* initOnly */);
}
@@ -55,17 +54,12 @@
private final float mStart;
private final float mEnd;
- private final float mDelay;
- private final float mDuration;
private final Interpolator mInterpolator;
- public FloatProp(float start, float end, float delay, float duration, Interpolator i) {
+ public FloatProp(float start, float end, Interpolator i) {
value = mStart = start;
mEnd = end;
- mDelay = delay;
- mDuration = duration;
mInterpolator = i;
-
mAllProperties.add(this);
}
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
deleted file mode 100644
index 9418512..0000000
--- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
-
-import android.view.Surface;
-
-import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.NavigationMode;
-
-/**
- * Utility class to check nav bar position.
- */
-public class NavBarPosition {
-
- private final boolean mIsTablet;
- private final NavigationMode mMode;
- private final int mDisplayRotation;
-
- public NavBarPosition(NavigationMode mode, Info info) {
- mIsTablet = info.isTablet(info.realBounds);
- mMode = mode;
- mDisplayRotation = info.rotation;
- }
-
- public boolean isRightEdge() {
- return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90 && !mIsTablet;
- }
-
- public boolean isLeftEdge() {
- return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270 && !mIsTablet;
- }
-
- public float getRotation() {
- return isLeftEdge() ? 90 : (isRightEdge() ? -90 : 0);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.kt b/quickstep/src/com/android/quickstep/util/NavBarPosition.kt
new file mode 100644
index 0000000..43cf540
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util
+
+import android.view.Surface
+import com.android.launcher3.util.DisplayController.Info
+import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.NavigationMode.NO_BUTTON
+
+/** Utility class to check nav bar position. */
+data class NavBarPosition(
+ val isTablet: Boolean,
+ val displayRotation: Int,
+ val mode: NavigationMode
+) {
+ constructor(
+ mode: NavigationMode,
+ info: Info
+ ) : this(info.isTablet(info.realBounds), info.rotation, mode)
+
+ val isRightEdge: Boolean
+ get() = mode != NO_BUTTON && displayRotation == Surface.ROTATION_90 && !isTablet
+ val isLeftEdge: Boolean
+ get() = mode != NO_BUTTON && displayRotation == Surface.ROTATION_270 && !isTablet
+
+ val rotation: Float
+ get() = if (isLeftEdge) 90f else if (isRightEdge) -90f else 0f
+}
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 132d1c1..492c801 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -62,7 +62,7 @@
* {@link WorkspaceRevealAnim}.
*/
public void animateWithVelocity(float velocity) {
- StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+ StateManager<LauncherState, Launcher> stateManager = mLauncher.getStateManager();
LauncherState startState = stateManager.getState();
if (startState != OVERVIEW) {
Log.e(TAG, "animateFromOverviewToHome: unexpected start state " + startState);
diff --git a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
index 3d9e09e..cbe0f19 100644
--- a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
@@ -27,6 +27,8 @@
public int getPlaceholderIconFadeInEnd() { return 133; }
public int getStagedRectSlideStart() { return 0; }
public int getStagedRectSlideEnd() { return 333; }
+ public int getBackingScrimFadeInStart() { return 0; }
+ public int getBackingScrimFadeInEnd() { return 266; }
public int getDuration() { return PHONE_CONFIRM_DURATION; }
}
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 9df568e..cfe5b2c 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -27,6 +27,7 @@
import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
@@ -48,7 +49,7 @@
* Sets up the initial onboarding behavior for the launcher
*/
public static void setup(QuickstepLauncher launcher) {
- StateManager<LauncherState> stateManager = launcher.getStateManager();
+ StateManager<LauncherState, Launcher> stateManager = launcher.getStateManager();
if (!HOME_BOUNCE_SEEN.get(launcher)) {
stateManager.addStateListener(new StateListener<LauncherState>() {
@Override
@@ -136,21 +137,16 @@
});
}
- if (!ALL_APPS_VISITED_COUNT.hasReachedMax(launcher)) {
+ if (!Utilities.isRunningInTestHarness()) {
launcher.getStateManager().addStateListener(new StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState == ALL_APPS) {
ALL_APPS_VISITED_COUNT.increment(launcher);
- return;
}
-
- boolean hasReachedMaxCount = ALL_APPS_VISITED_COUNT.hasReachedMax(launcher);
- launcher.getAppsView().getFloatingHeaderView().findFixedRowByType(
- AppsDividerView.class).setShowAllAppsLabel(!hasReachedMaxCount);
- if (hasReachedMaxCount) {
- launcher.getStateManager().removeStateListener(this);
- }
+ launcher.getAppsView().getFloatingHeaderView()
+ .findFixedRowByType(AppsDividerView.class)
+ .setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(launcher));
}
});
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
index 1d008da..0b05c2e 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
@@ -19,39 +19,40 @@
import android.animation.Animator;
import android.animation.ObjectAnimator;
+import android.content.Context;
import androidx.dynamicanimation.animation.DynamicAnimation;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
-import com.android.launcher3.statemanager.StatefulActivity;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
-public class RecentsAtomicAnimationFactory<ACTIVITY_TYPE extends StatefulActivity, STATE_TYPE>
- extends AtomicAnimationFactory<STATE_TYPE> {
+public class RecentsAtomicAnimationFactory<CONTAINER extends Context & RecentsViewContainer,
+ STATE_TYPE> extends AtomicAnimationFactory<STATE_TYPE> {
public static final int INDEX_RECENTS_FADE_ANIM = AtomicAnimationFactory.NEXT_INDEX + 0;
public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = AtomicAnimationFactory.NEXT_INDEX + 1;
private static final int MY_ANIM_COUNT = 2;
- protected final ACTIVITY_TYPE mActivity;
+ protected final CONTAINER mContainer;
- public RecentsAtomicAnimationFactory(ACTIVITY_TYPE activity) {
+ public RecentsAtomicAnimationFactory(CONTAINER container) {
super(MY_ANIM_COUNT);
- mActivity = activity;
+ mContainer = container;
}
@Override
public Animator createStateElementAnimation(int index, float... values) {
switch (index) {
case INDEX_RECENTS_FADE_ANIM:
- ObjectAnimator alpha = ObjectAnimator.ofFloat(mActivity.getOverviewPanel(),
+ ObjectAnimator alpha = ObjectAnimator.ofFloat(mContainer.getOverviewPanel(),
RecentsView.CONTENT_ALPHA, values);
return alpha;
case INDEX_RECENTS_TRANSLATE_X_ANIM: {
- RecentsView rv = mActivity.getOverviewPanel();
- return new SpringAnimationBuilder(mActivity)
+ RecentsView rv = mContainer.getOverviewPanel();
+ return new SpringAnimationBuilder(mContainer)
.setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE)
.setDampingRatio(0.8f)
.setStiffness(250)
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index cba628b..9335e7e 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -50,7 +50,7 @@
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SettingsCache;
-import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
@@ -118,7 +118,7 @@
| FLAG_SWIPE_UP_NOT_RUNNING;
private final Context mContext;
- private final BaseActivityInterface mActivityInterface;
+ private final BaseContainerInterface mContainerInterface;
private final OrientationEventListener mOrientationListener;
private final SettingsCache mSettingsCache;
private final SettingsCache.OnChangeListener mRotationChangeListener =
@@ -138,10 +138,10 @@
* is enabled
* @see #setRotationWatcherEnabled(boolean)
*/
- public RecentsOrientedState(Context context, BaseActivityInterface activityInterface,
+ public RecentsOrientedState(Context context, BaseContainerInterface containerInterface,
IntConsumer rotationChangeListener) {
mContext = context;
- mActivityInterface = activityInterface;
+ mContainerInterface = containerInterface;
mOrientationListener = new OrientationEventListener(context) {
@Override
public void onOrientationChanged(int degrees) {
@@ -153,7 +153,7 @@
}
};
- mFlags = mActivityInterface.rotationSupportedByActivity
+ mFlags = mContainerInterface.rotationSupportedByActivity
? FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY : 0;
mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
@@ -161,8 +161,8 @@
initFlags();
}
- public BaseActivityInterface getActivityInterface() {
- return mActivityInterface;
+ public BaseContainerInterface getContainerInterface() {
+ return mContainerInterface;
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
index 245dde2..00b4011 100644
--- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.animation.Animator;
@@ -104,6 +106,8 @@
private float mCurrentScaleProgress;
private FlingSpringAnim mRectXAnim;
private FlingSpringAnim mRectYAnim;
+ private SpringAnimation mRectXSpring;
+ private SpringAnimation mRectYSpring;
private SpringAnimation mRectScaleAnim;
private boolean mAnimsStarted;
private boolean mRectXAnimEnded;
@@ -153,6 +157,10 @@
mCurrentY = getTrackedYFromRect(mStartRect);
}
+ public RectF getTargetRect() {
+ return mTargetRect;
+ }
+
private float getTrackedYFromRect(RectF rect) {
switch (mTracking) {
case TRACKING_TOP:
@@ -166,27 +174,53 @@
}
public void onTargetPositionChanged() {
- if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) {
- mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX());
- }
+ if (enableScalingRevealHomeAnimation()) {
+ if (isEnded()) {
+ return;
+ }
- if (mRectYAnim != null) {
- switch (mTracking) {
- case TRACKING_TOP:
- if (mRectYAnim.getTargetPosition() != mTargetRect.top) {
- mRectYAnim.updatePosition(mCurrentY, mTargetRect.top);
- }
- break;
- case TRACKING_BOTTOM:
- if (mRectYAnim.getTargetPosition() != mTargetRect.bottom) {
- mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom);
- }
- break;
- case TRACKING_CENTER:
- if (mRectYAnim.getTargetPosition() != mTargetRect.centerY()) {
- mRectYAnim.updatePosition(mCurrentY, mTargetRect.centerY());
- }
- break;
+ if (mRectXSpring != null) {
+ mRectXSpring.animateToFinalPosition(mTargetRect.centerX());
+ mRectXAnimEnded = false;
+ }
+
+ if (mRectYSpring != null) {
+ switch (mTracking) {
+ case TRACKING_TOP:
+ mRectYSpring.animateToFinalPosition(mTargetRect.top);
+ break;
+ case TRACKING_BOTTOM:
+ mRectYSpring.animateToFinalPosition(mTargetRect.bottom);
+ break;
+ case TRACKING_CENTER:
+ mRectYSpring.animateToFinalPosition(mTargetRect.centerY());
+ break;
+ }
+ mRectYAnimEnded = false;
+ }
+ } else {
+ if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) {
+ mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX());
+ }
+
+ if (mRectYAnim != null) {
+ switch (mTracking) {
+ case TRACKING_TOP:
+ if (mRectYAnim.getTargetPosition() != mTargetRect.top) {
+ mRectYAnim.updatePosition(mCurrentY, mTargetRect.top);
+ }
+ break;
+ case TRACKING_BOTTOM:
+ if (mRectYAnim.getTargetPosition() != mTargetRect.bottom) {
+ mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom);
+ }
+ break;
+ case TRACKING_CENTER:
+ if (mRectYAnim.getTargetPosition() != mTargetRect.centerY()) {
+ mRectYAnim.updatePosition(mCurrentY, mTargetRect.centerY());
+ }
+ break;
+ }
}
}
}
@@ -215,59 +249,127 @@
maybeOnEnd();
});
- // We dampen the user velocity here to keep the natural feeling and to prevent the
- // rect from straying too from a linear path.
- final float xVelocityPxPerS = velocityPxPerMs.x * 1000;
- final float yVelocityPxPerS = velocityPxPerMs.y * 1000;
- final float dampedXVelocityPxPerS = OverScroll.dampedScroll(
- Math.abs(xVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(xVelocityPxPerS);
- final float dampedYVelocityPxPerS = OverScroll.dampedScroll(
- Math.abs(yVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(yVelocityPxPerS);
-
+ float xVelocityPxPerS = velocityPxPerMs.x * 1000;
+ float yVelocityPxPerS = velocityPxPerMs.y * 1000;
float startX = mCurrentCenterX;
float endX = mTargetRect.centerX();
- float minXValue = Math.min(startX, endX);
- float maxXValue = Math.max(startX, endX);
-
- mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX,
- dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, mDampingX, mStiffnessX,
- onXEndListener);
-
float startY = mCurrentY;
float endY = getTrackedYFromRect(mTargetRect);
- float minYValue = Math.min(startY, endY);
- float maxYValue = Math.max(startY, endY);
- mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY, dampedYVelocityPxPerS,
- mMinVisChange, minYValue, maxYValue, mDampingY, mStiffnessY, onYEndListener);
-
float minVisibleChange = Math.abs(1f / mStartRect.height());
- ResourceProvider rp = DynamicResource.provider(context);
- float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio);
- // Increase the stiffness for devices where we want the window size to transform quicker.
- boolean shouldUseHigherStiffness = profile != null
- && (profile.isLandscape || profile.isTablet);
- float stiffness = shouldUseHigherStiffness
- ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness)
- : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness);
+ if (enableScalingRevealHomeAnimation()) {
+ ResourceProvider rp = DynamicResource.provider(context);
+ long minVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_x_px_per_s);
+ long maxVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_x_px_per_s);
+ long minVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_y_px_per_s);
+ long maxVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_y_px_per_s);
+ float fallOffFactor = rp.getFloat(R.dimen.swipe_up_max_velocity_fall_off_factor);
- mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
- .setSpring(new SpringForce(1f)
- .setDampingRatio(damping)
- .setStiffness(stiffness))
- .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
- .setMaxValue(1f)
- .setMinimumVisibleChange(minVisibleChange)
- .addEndListener((animation, canceled, value, velocity) -> {
- mRectScaleAnimEnded = true;
- maybeOnEnd();
- });
+ // We want the actual initial velocity to never dip below the minimum, and to taper off
+ // once it's above the soft cap so that we can prevent the window from flying off
+ // screen, while maintaining a natural feel.
+ xVelocityPxPerS = adjustVelocity(
+ xVelocityPxPerS, minVelocityXPxPerS, maxVelocityXPxPerS, fallOffFactor);
+ yVelocityPxPerS = adjustVelocity(
+ yVelocityPxPerS, minVelocityYPxPerS, maxVelocityYPxPerS, fallOffFactor);
- setCanRelease(false);
- mAnimsStarted = true;
+ float stiffnessX = rp.getFloat(R.dimen.swipe_up_rect_x_stiffness);
+ float dampingX = rp.getFloat(R.dimen.swipe_up_rect_x_damping_ratio);
+ mRectXSpring =
+ new SpringAnimation(this, RECT_CENTER_X)
+ .setSpring(
+ new SpringForce(endX)
+ .setStiffness(stiffnessX)
+ .setDampingRatio(dampingX)
+ ).setStartValue(startX)
+ .setStartVelocity(xVelocityPxPerS)
+ .addEndListener(onXEndListener);
- mRectXAnim.start();
- mRectYAnim.start();
+ float stiffnessY = rp.getFloat(R.dimen.swipe_up_rect_y_stiffness);
+ float dampingY = rp.getFloat(R.dimen.swipe_up_rect_y_damping_ratio);
+ mRectYSpring =
+ new SpringAnimation(this, RECT_Y)
+ .setSpring(
+ new SpringForce(endY)
+ .setStiffness(stiffnessY)
+ .setDampingRatio(dampingY)
+ )
+ .setStartValue(startY)
+ .setStartVelocity(yVelocityPxPerS)
+ .addEndListener(onYEndListener);
+
+ float stiffnessZ = rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness_v2);
+ float dampingZ = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio_v2);
+ mRectScaleAnim =
+ new SpringAnimation(this, RECT_SCALE_PROGRESS)
+ .setSpring(
+ new SpringForce(1f)
+ .setStiffness(stiffnessZ)
+ .setDampingRatio(dampingZ))
+ .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
+ .setMaxValue(1f)
+ .setMinimumVisibleChange(minVisibleChange)
+ .addEndListener((animation, canceled, value, velocity) -> {
+ mRectScaleAnimEnded = true;
+ maybeOnEnd();
+ });
+
+ setCanRelease(false);
+ mAnimsStarted = true;
+
+ mRectXSpring.start();
+ mRectYSpring.start();
+ } else {
+ // We dampen the user velocity here to keep the natural feeling and to prevent the
+ // rect from straying too from a linear path.
+ final float dampedXVelocityPxPerS = OverScroll.dampedScroll(
+ Math.abs(xVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(xVelocityPxPerS);
+ final float dampedYVelocityPxPerS = OverScroll.dampedScroll(
+ Math.abs(yVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(yVelocityPxPerS);
+
+ float minXValue = Math.min(startX, endX);
+ float maxXValue = Math.max(startX, endX);
+
+ mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX,
+ dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, mDampingX,
+ mStiffnessX, onXEndListener);
+
+ float minYValue = Math.min(startY, endY);
+ float maxYValue = Math.max(startY, endY);
+ mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY,
+ dampedYVelocityPxPerS, mMinVisChange, minYValue, maxYValue, mDampingY,
+ mStiffnessY, onYEndListener);
+
+ ResourceProvider rp = DynamicResource.provider(context);
+ float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio);
+
+ // Increase the stiffness for devices where we want the window size to transform
+ // quicker.
+ boolean shouldUseHigherStiffness = profile != null
+ && (profile.isLandscape || profile.isTablet);
+ float stiffness = shouldUseHigherStiffness
+ ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness)
+ : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness);
+
+ mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
+ .setSpring(new SpringForce(1f)
+ .setDampingRatio(damping)
+ .setStiffness(stiffness))
+ .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
+ .setMaxValue(1f)
+ .setMinimumVisibleChange(minVisibleChange)
+ .addEndListener((animation, canceled, value, velocity) -> {
+ mRectScaleAnimEnded = true;
+ maybeOnEnd();
+ });
+
+ setCanRelease(false);
+ mAnimsStarted = true;
+
+ mRectXAnim.start();
+ mRectYAnim.start();
+ }
+
mRectScaleAnim.start();
for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
animatorListener.onAnimationStart(null);
@@ -276,8 +378,17 @@
public void end() {
if (mAnimsStarted) {
- mRectXAnim.end();
- mRectYAnim.end();
+ if (enableScalingRevealHomeAnimation()) {
+ if (mRectXSpring.canSkipToEnd()) {
+ mRectXSpring.skipToEnd();
+ }
+ if (mRectYSpring.canSkipToEnd()) {
+ mRectYSpring.skipToEnd();
+ }
+ } else {
+ mRectXAnim.end();
+ mRectYAnim.end();
+ }
if (mRectScaleAnim.canSkipToEnd()) {
mRectScaleAnim.skipToEnd();
}
@@ -357,6 +468,32 @@
end();
}
+ /**
+ * Modify the given velocity so that it's never below the minimum value, and falls off by the
+ * given factor once it goes above the maximum value.
+ * In order for the max soft cap to be enforced, the fall-off factor must be >1.
+ */
+ private static float adjustVelocity(float velocity, long min, long max, float factor) {
+ float sign = Math.signum(velocity);
+ float magnitude = Math.abs(velocity);
+
+ // If the absolute velocity is less than the min, bump it up.
+ if (magnitude < min) {
+ return min * sign;
+ }
+
+ // If the absolute velocity falls between min and max, or the fall-off factor is invalid,
+ // do nothing.
+ if (magnitude <= max || factor <= 1) {
+ return velocity;
+ }
+
+ // Scale the excess velocity by the fall-off factor.
+ float excess = magnitude - max;
+ float scaled = (float) Math.pow(excess, 1f / factor);
+ return (max + scaled) * sign;
+ }
+
public interface OnUpdateListener {
/**
* Called when an update is made to the animation.
@@ -432,7 +569,11 @@
final float bottomThreshold = deviceProfile.heightPx - padding.bottom;
if (targetRect.bottom > bottomThreshold) {
- tracking = TRACKING_BOTTOM;
+ if (enableScalingRevealHomeAnimation()) {
+ tracking = TRACKING_CENTER;
+ } else {
+ tracking = TRACKING_BOTTOM;
+ }
} else if (targetRect.top < topThreshold) {
tracking = TRACKING_TOP;
} else {
diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
new file mode 100644
index 0000000..f547a7fb
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.animation.AnimatorSet
+import android.graphics.Matrix
+import android.graphics.Path
+import android.graphics.RectF
+import android.view.View
+import android.view.animation.PathInterpolator
+import androidx.core.graphics.transform
+import com.android.app.animation.Interpolators
+import com.android.app.animation.Interpolators.LINEAR
+import com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY
+import com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WORKSPACE_STATE
+import com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY
+import com.android.launcher3.LauncherState
+import com.android.launcher3.anim.AnimatorListeners
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.anim.PropertySetter
+import com.android.launcher3.states.StateAnimationConfig
+import com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER
+import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
+import com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM
+import com.android.launcher3.uioverrides.QuickstepLauncher
+import com.android.quickstep.views.RecentsView
+
+/**
+ * Creates an animation where the workspace and hotseat fade in while revealing from the center of
+ * the screen outwards radially. This is used in conjunction with the swipe up to home animation.
+ */
+class ScalingWorkspaceRevealAnim(
+ launcher: QuickstepLauncher,
+ siblingAnimation: RectFSpringAnim?,
+ windowTargetRect: RectF?
+) {
+ companion object {
+ private const val FADE_DURATION_MS = 200L
+ private const val SCALE_DURATION_MS = 1000L
+ private const val MAX_ALPHA = 1f
+ private const val MIN_ALPHA = 0f
+ private const val MAX_SIZE = 1f
+ private const val MIN_SIZE = 0.85f
+
+ /**
+ * Custom interpolator for both the home and wallpaper scaling. Necessary because EMPHASIZED
+ * is too aggressive, but EMPHASIZED_DECELERATE is too soft.
+ */
+ private val SCALE_INTERPOLATOR =
+ PathInterpolator(
+ Path().apply {
+ moveTo(0f, 0f)
+ cubicTo(0.045f, 0.0356f, 0.0975f, 0.2055f, 0.15f, 0.3952f)
+ cubicTo(0.235f, 0.6855f, 0.235f, 1f, 1f, 1f)
+ }
+ )
+ }
+
+ private val animation = PendingAnimation(SCALE_DURATION_MS)
+
+ init {
+ // Make sure the starting state is right for the animation.
+ val setupConfig = StateAnimationConfig()
+ setupConfig.animFlags = SKIP_OVERVIEW.or(SKIP_DEPTH_CONTROLLER).or(SKIP_SCRIM)
+ setupConfig.duration = 0
+ launcher.stateManager
+ .createAtomicAnimation(LauncherState.BACKGROUND_APP, LauncherState.NORMAL, setupConfig)
+ .start()
+ launcher
+ .getOverviewPanel<RecentsView<QuickstepLauncher, LauncherState>>()
+ .forceFinishScroller()
+ launcher.workspace.stateTransitionAnimation.setScrim(
+ PropertySetter.NO_ANIM_PROPERTY_SETTER,
+ LauncherState.BACKGROUND_APP,
+ setupConfig
+ )
+
+ val workspace = launcher.workspace
+ val hotseat = launcher.hotseat
+
+ // Scale the Workspace and Hotseat around the same pivot.
+ workspace.setPivotToScaleWithSelf(hotseat)
+ animation.addFloat(
+ workspace,
+ WORKSPACE_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE],
+ MIN_SIZE,
+ MAX_SIZE,
+ SCALE_INTERPOLATOR,
+ )
+ animation.addFloat(
+ hotseat,
+ HOTSEAT_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE],
+ MIN_SIZE,
+ MAX_SIZE,
+ SCALE_INTERPOLATOR,
+ )
+
+ // Fade in quickly at the beginning of the animation, so the content doesn't look like it's
+ // popping into existence out of nowhere.
+ val fadeClamp = FADE_DURATION_MS.toFloat() / SCALE_DURATION_MS
+ workspace.alpha = MIN_ALPHA
+ animation.setViewAlpha(
+ workspace,
+ MAX_ALPHA,
+ Interpolators.clampToProgress(LINEAR, 0f, fadeClamp)
+ )
+ hotseat.alpha = MIN_ALPHA
+ animation.setViewAlpha(
+ hotseat,
+ MAX_ALPHA,
+ Interpolators.clampToProgress(LINEAR, 0f, fadeClamp)
+ )
+
+ val transitionConfig = StateAnimationConfig()
+
+ // Match the Wallpaper animation to the rest of the content.
+ val depthController = (launcher as? QuickstepLauncher)?.depthController
+ transitionConfig.setInterpolator(StateAnimationConfig.ANIM_DEPTH, SCALE_INTERPOLATOR)
+ depthController?.setStateWithAnimation(LauncherState.NORMAL, transitionConfig, animation)
+
+ // Make sure that the contrast scrim animates correctly if needed.
+ transitionConfig.setInterpolator(StateAnimationConfig.ANIM_SCRIM_FADE, SCALE_INTERPOLATOR)
+ launcher.workspace.stateTransitionAnimation.setScrim(
+ animation,
+ LauncherState.NORMAL,
+ transitionConfig
+ )
+
+ // To avoid awkward jumps in icon position, we want the sibling animation to always be
+ // targeting the current position. Since we can't easily access this, instead we calculate
+ // it using the animation of the whole of home.
+ // We start by caching the final target position, as this is the base for the transforms.
+ val originalTarget = RectF(windowTargetRect)
+ animation.addOnFrameListener {
+ val transformed = RectF(originalTarget)
+
+ // First we scale down using the same pivot as the workspace scale, so we find the
+ // correct position AND size.
+ transformed.transform(
+ Matrix().apply {
+ setScale(workspace.scaleX, workspace.scaleY, workspace.pivotX, workspace.pivotY)
+ }
+ )
+ // Then we scale back up around the center of the current position. This is because the
+ // icon animation behaves poorly if it is given a target that is smaller than the size
+ // of the icon.
+ transformed.transform(
+ Matrix().apply {
+ setScale(
+ 1 / workspace.scaleX,
+ 1 / workspace.scaleY,
+ transformed.centerX(),
+ transformed.centerY()
+ )
+ }
+ )
+
+ if (transformed != windowTargetRect) {
+ windowTargetRect?.set(transformed)
+ siblingAnimation?.onTargetPositionChanged()
+ }
+ }
+
+ // Needed to avoid text artefacts during the scale animation.
+ workspace.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ animation.addListener(
+ AnimatorListeners.forEndCallback(
+ Runnable {
+ workspace.setLayerType(View.LAYER_TYPE_NONE, null)
+ hotseat.setLayerType(View.LAYER_TYPE_NONE, null)
+ }
+ )
+ )
+ }
+
+ fun getAnimators(): AnimatorSet {
+ return animation.buildAnim()
+ }
+
+ fun start() {
+ getAnimators().start()
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
index 1347291..dbeedd3 100644
--- a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
+++ b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
@@ -23,8 +23,8 @@
import android.os.RemoteException
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
-import android.window.IRemoteTransition
import android.window.IRemoteTransitionFinishedCallback
+import android.window.RemoteTransitionStub
import android.window.TransitionInfo
import com.android.launcher3.anim.AnimatorListeners.forEndCallback
import com.android.launcher3.util.Executors
@@ -36,19 +36,9 @@
private val pageSpacing: Int,
private val cornerRadius: Float,
private val interpolator: TimeInterpolator,
-) : IRemoteTransition.Stub() {
+) : RemoteTransitionStub() {
private val animationDurationMs = 500L
- override fun mergeAnimation(
- iBinder: IBinder,
- transitionInfo: TransitionInfo,
- transaction: Transaction,
- mergeTarget: IBinder,
- finishCB: IRemoteTransitionFinishedCallback
- ) {
- // Do not report finish if we don't know how to handle this transition.
- }
-
override fun startAnimation(
transition: IBinder,
info: TransitionInfo,
@@ -116,6 +106,4 @@
Executors.MAIN_EXECUTOR.execute { anim.start() }
}
-
- override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index b7b1d8f..7ea04b1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -23,7 +23,9 @@
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
import android.graphics.RectF
@@ -41,17 +43,19 @@
import com.android.app.animation.Interpolators
import com.android.launcher3.DeviceProfile
import com.android.launcher3.Flags.enableOverviewIconMenu
-import com.android.launcher3.Launcher
+import com.android.launcher3.InsettableFrameLayout
import com.android.launcher3.QuickstepTransitionManager
+import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.apppairs.AppPairIcon
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.logging.StatsLogManager.EventEnum
+import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
-import com.android.launcher3.statemanager.StatefulActivity
import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.uioverrides.QuickstepLauncher
import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
import com.android.launcher3.views.BaseDragLayer
@@ -61,78 +65,97 @@
import com.android.quickstep.views.GroupedTaskView
import com.android.quickstep.views.IconAppChipView
import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.SplitInstructionsView
-import com.android.quickstep.views.TaskThumbnailView
+import com.android.quickstep.views.TaskThumbnailViewDeprecated
import com.android.quickstep.views.TaskView
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.quickstep.views.TaskView.TaskContainer
import com.android.quickstep.views.TaskViewIcon
+import com.android.wm.shell.shared.TransitionUtil
import java.util.Optional
import java.util.function.Supplier
/**
- * Utils class to help run animations for initiating split screen from launcher.
- * Will be expanded with future refactors. Works in conjunction with the state stored in
- * [SplitSelectStateController]
+ * Utils class to help run animations for initiating split screen from launcher. Will be expanded
+ * with future refactors. Works in conjunction with the state stored in [SplitSelectStateController]
*/
class SplitAnimationController(val splitSelectStateController: SplitSelectStateController) {
companion object {
// Break this out into maybe enums? Abstractions into its own classes? Tbd.
data class SplitAnimInitProps(
- val originalView: View,
- val originalBitmap: Bitmap?,
- val iconDrawable: Drawable,
- val fadeWithThumbnail: Boolean,
- val isStagedTask: Boolean,
- val iconView: View?
+ val originalView: View,
+ val originalBitmap: Bitmap?,
+ val iconDrawable: Drawable,
+ val fadeWithThumbnail: Boolean,
+ val isStagedTask: Boolean,
+ val iconView: View?
)
}
/**
- * Returns different elements to animate for the initial split selection animation
- * depending on the state of the surface from which the split was initiated
+ * Returns different elements to animate for the initial split selection animation depending on
+ * the state of the surface from which the split was initiated
*/
- fun getFirstAnimInitViews(taskViewSupplier: Supplier<TaskView>,
- splitSelectSourceSupplier: Supplier<SplitSelectSource?>)
- : SplitAnimInitProps {
+ fun getFirstAnimInitViews(
+ taskViewSupplier: Supplier<TaskView>,
+ splitSelectSourceSupplier: Supplier<SplitSelectSource?>
+ ): SplitAnimInitProps {
val splitSelectSource = splitSelectSourceSupplier.get()
if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
// Initiating from home
- return SplitAnimInitProps(splitSelectSource!!.view, originalBitmap = null,
- splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true,
- iconView = null)
+ return SplitAnimInitProps(
+ splitSelectSource!!.view,
+ originalBitmap = null,
+ splitSelectSource.drawable,
+ fadeWithThumbnail = false,
+ isStagedTask = true,
+ iconView = null
+ )
} else if (splitSelectStateController.isDismissingFromSplitPair) {
// Initiating split from overview, but on a split pair
val taskView = taskViewSupplier.get()
- for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
+ for (container: TaskContainer in taskView.taskContainers) {
if (container.task.getKey().getId() == splitSelectStateController.initialTaskId) {
val drawable = getDrawable(container.iconView, splitSelectSource)
- return SplitAnimInitProps(container.thumbnailView,
- container.thumbnailView.thumbnail, drawable!!,
- fadeWithThumbnail = true, isStagedTask = true,
- iconView = container.iconView.asView()
+ return SplitAnimInitProps(
+ container.thumbnailViewDeprecated,
+ container.thumbnailViewDeprecated.thumbnail,
+ drawable!!,
+ fadeWithThumbnail = true,
+ isStagedTask = true,
+ iconView = container.iconView.asView()
)
}
}
- throw IllegalStateException("Attempting to init split from existing split pair " +
- "without a valid taskIdAttributeContainer")
+ throw IllegalStateException(
+ "Attempting to init split from existing split pair " +
+ "without a valid taskIdAttributeContainer"
+ )
} else {
// Initiating split from overview on fullscreen task TaskView
val taskView = taskViewSupplier.get()
- val drawable = getDrawable(taskView.iconView, splitSelectSource)
- return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail,
- drawable!!, fadeWithThumbnail = true, isStagedTask = true,
- taskView.iconView.asView()
- )
+ taskView.taskContainers.first().let {
+ val drawable = getDrawable(it.iconView, splitSelectSource)
+ return SplitAnimInitProps(
+ it.thumbnailViewDeprecated,
+ it.thumbnailViewDeprecated.thumbnail,
+ drawable!!,
+ fadeWithThumbnail = true,
+ isStagedTask = true,
+ iconView = it.iconView.asView()
+ )
+ }
}
}
/**
- * Returns the drawable that's provided in iconView, however if that
- * is null it falls back to the drawable that's in splitSelectSource.
- * TaskView's icon drawable can be null if the TaskView is scrolled far enough off screen
+ * Returns the drawable that's provided in iconView, however if that is null it falls back to
+ * the drawable that's in splitSelectSource. TaskView's icon drawable can be null if the
+ * TaskView is scrolled far enough off screen
+ *
* @return [Drawable]
*/
- fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?) : Drawable? {
+ fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?): Drawable? {
if (iconView.drawable == null && splitSelectSource != null) {
return splitSelectSource.drawable
}
@@ -140,24 +163,28 @@
}
/**
- * When selecting first app from split pair, second app's thumbnail remains. This animates
- * the second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying
- * it with [TaskThumbnailView]'s splashView. Adds animations to the provided builder.
+ * When selecting first app from split pair, second app's thumbnail remains. This animates the
+ * second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying it
+ * with [TaskThumbnailViewDeprecated]'s splashView. Adds animations to the provided builder.
* Note: The app that **was not** selected as the first split app should be the container that's
* passed through.
*
* @param builder Adds animation to this
* @param taskIdAttributeContainer container of the app that **was not** selected
* @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair
- * (opposite of that representing [taskIdAttributeContainer])
+ * (opposite of that representing [taskIdAttributeContainer])
*/
- fun addInitialSplitFromPair(taskIdAttributeContainer: TaskIdAttributeContainer,
- builder: PendingAnimation, deviceProfile: DeviceProfile,
- taskViewWidth: Int, taskViewHeight: Int,
- isPrimaryTaskSplitting: Boolean) {
- val thumbnail = taskIdAttributeContainer.thumbnailView
+ fun addInitialSplitFromPair(
+ taskIdAttributeContainer: TaskContainer,
+ builder: PendingAnimation,
+ deviceProfile: DeviceProfile,
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ isPrimaryTaskSplitting: Boolean
+ ) {
+ val thumbnail = taskIdAttributeContainer.thumbnailViewDeprecated
val iconView: View = taskIdAttributeContainer.iconView.asView()
- builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
+ builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailViewDeprecated.SPLASH_ALPHA, 1f))
thumbnail.setShowSplashForSplitSelection(true)
// With the new `IconAppChipView`, we always want to keep the chip pinned to the
// top left of the task / thumbnail.
@@ -170,35 +197,42 @@
)
)
builder.add(
- ObjectAnimator.ofFloat(
- iconView.splitTranslationY,
- MULTI_PROPERTY_VALUE,
- 0f
- )
+ ObjectAnimator.ofFloat(iconView.splitTranslationY, MULTI_PROPERTY_VALUE, 0f)
)
}
if (deviceProfile.isLeftRightSplit) {
// Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width
- builder.add(ObjectAnimator.ofFloat(thumbnail,
- TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX))
+ builder.add(
+ ObjectAnimator.ofFloat(
+ thumbnail,
+ TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_X,
+ centerThumbnailTranslationX
+ )
+ )
if (!enableOverviewIconMenu()) {
// icons are anchored from Gravity.END, so need to use negative translation
val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
- builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
- -centerIconTranslationX))
+ builder.add(
+ ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, -centerIconTranslationX)
+ )
}
builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
// Reset other dimensions
// TODO(b/271468547), can't set Y translate to 0, need to account for top space
thumbnail.scaleY = 1f
- val translateYResetVal: Float = if (!isPrimaryTaskSplitting) 0f else
- deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
- builder.add(ObjectAnimator.ofFloat(thumbnail,
- TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y,
- translateYResetVal))
+ val translateYResetVal: Float =
+ if (!isPrimaryTaskSplitting) 0f
+ else deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
+ builder.add(
+ ObjectAnimator.ofFloat(
+ thumbnail,
+ TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_Y,
+ translateYResetVal
+ )
+ )
} else {
val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
// Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
@@ -214,16 +248,21 @@
// translations otherwise this asymmetry causes problems..
if (isPrimaryTaskSplitting) {
centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
- centerThumbnailTranslationY += deviceProfile.overviewTaskThumbnailTopMarginPx
- .toFloat()
+ centerThumbnailTranslationY +=
+ deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
} else {
centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
}
val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height
- builder.add(ObjectAnimator.ofFloat(thumbnail,
- TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY))
+ builder.add(
+ ObjectAnimator.ofFloat(
+ thumbnail,
+ TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_Y,
+ centerThumbnailTranslationY
+ )
+ )
- if (!enableOverviewIconMenu()) {
+ if (!enableOverviewIconMenu()) {
// icons are anchored from Gravity.END, so need to use negative translation
builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, 0f))
}
@@ -231,18 +270,65 @@
// Reset other dimensions
thumbnail.scaleX = 1f
- builder.add(ObjectAnimator.ofFloat(thumbnail,
- TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f))
+ builder.add(
+ ObjectAnimator.ofFloat(
+ thumbnail,
+ TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_X,
+ 0f
+ )
+ )
}
}
+ /**
+ * Creates and returns a fullscreen scrim to fade in behind the split confirm animation, and
+ * adds it to the provided [pendingAnimation].
+ */
+ fun addScrimBehindAnim(
+ pendingAnimation: PendingAnimation,
+ container: RecentsViewContainer,
+ context: Context
+ ): View {
+ val scrim = View(context)
+ val recentsView = container.getOverviewPanel<RecentsView<*, *>>()
+ val dp: DeviceProfile = container.getDeviceProfile()
+ // Add it before/under the most recently added first floating taskView
+ val firstAddedSplitViewIndex: Int =
+ container
+ .getDragLayer()
+ .indexOfChild(recentsView.splitSelectController.firstFloatingTaskView)
+ container.getDragLayer().addView(scrim, firstAddedSplitViewIndex)
+ // Make the scrim fullscreen
+ val lp = scrim.layoutParams as InsettableFrameLayout.LayoutParams
+ lp.topMargin = 0
+ lp.height = dp.heightPx
+ lp.width = dp.widthPx
+
+ scrim.alpha = 0f
+ scrim.setBackgroundColor(
+ container.asContext().resources.getColor(R.color.taskbar_background_dark)
+ )
+ val timings = AnimUtils.getDeviceSplitToConfirmTimings(dp.isTablet) as SplitToConfirmTimings
+ pendingAnimation.setViewAlpha(
+ scrim,
+ 1f,
+ Interpolators.clampToProgress(
+ timings.backingScrimFadeInterpolator,
+ timings.backingScrimFadeInStartOffset,
+ timings.backingScrimFadeInEndOffset
+ )
+ )
+
+ return scrim
+ }
+
/** Does not play any animation if user is not currently in split selection state. */
- fun playPlaceholderDismissAnim(launcher: StatefulActivity<*>, splitDismissEvent: EventEnum) {
+ fun playPlaceholderDismissAnim(container: RecentsViewContainer, splitDismissEvent: EventEnum) {
if (!splitSelectStateController.isSplitSelectActive) {
return
}
- val anim = createPlaceholderDismissAnim(launcher, splitDismissEvent, null /*duration*/)
+ val anim = createPlaceholderDismissAnim(container, splitDismissEvent, null /*duration*/)
anim.start()
}
@@ -250,102 +336,134 @@
* Returns [AnimatorSet] which slides initial split placeholder view offscreen and logs an event
* for why split is being dismissed
*/
- fun createPlaceholderDismissAnim(launcher: StatefulActivity<*>,
- splitDismissEvent: EventEnum,
- duration: Long?) : AnimatorSet {
+ fun createPlaceholderDismissAnim(
+ container: RecentsViewContainer,
+ splitDismissEvent: EventEnum,
+ duration: Long?
+ ): AnimatorSet {
val animatorSet = AnimatorSet()
duration?.let { animatorSet.duration = it }
- val recentsView : RecentsView<*, *> = launcher.getOverviewPanel()
- val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView
- ?: return animatorSet
+ val recentsView: RecentsView<*, *> = container.getOverviewPanel()
+ val floatingTask: FloatingTaskView =
+ splitSelectStateController.firstFloatingTaskView ?: return animatorSet
// We are in split selection state currently, transitioning to another state
- val dragLayer: BaseDragLayer<*> = launcher.dragLayer
+ val dragLayer: BaseDragLayer<*> = container.dragLayer
val onScreenRectF = RectF()
- Utilities.getBoundsForViewInDragLayer(dragLayer, floatingTask,
- Rect(0, 0, floatingTask.width, floatingTask.height),
- false, null, onScreenRectF)
+ Utilities.getBoundsForViewInDragLayer(
+ dragLayer,
+ floatingTask,
+ Rect(0, 0, floatingTask.width, floatingTask.height),
+ false,
+ null,
+ onScreenRectF
+ )
// Get the part of the floatingTask that intersects with the DragLayer (i.e. the
// on-screen portion)
onScreenRectF.intersect(
- dragLayer.left.toFloat(),
- dragLayer.top.toFloat(),
- dragLayer.right.toFloat(),
- dragLayer.bottom
- .toFloat()
+ dragLayer.left.toFloat(),
+ dragLayer.top.toFloat(),
+ dragLayer.right.toFloat(),
+ dragLayer.bottom.toFloat()
)
- animatorSet.play(ObjectAnimator.ofFloat(floatingTask,
+ animatorSet.play(
+ ObjectAnimator.ofFloat(
+ floatingTask,
FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN,
- recentsView.pagedOrientationHandler
- .getFloatingTaskOffscreenTranslationTarget(
- floatingTask,
- onScreenRectF,
- floatingTask.stagePosition,
- launcher.deviceProfile
- )))
- animatorSet.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- splitSelectStateController.resetState()
- safeRemoveViewFromDragLayer(launcher,
- splitSelectStateController.splitInstructionsView)
+ recentsView.pagedOrientationHandler.getFloatingTaskOffscreenTranslationTarget(
+ floatingTask,
+ onScreenRectF,
+ floatingTask.stagePosition,
+ container.deviceProfile
+ )
+ )
+ )
+ animatorSet.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ splitSelectStateController.resetState()
+ safeRemoveViewFromDragLayer(
+ container,
+ splitSelectStateController.splitInstructionsView
+ )
+ }
}
- })
+ )
splitSelectStateController.logExitReason(splitDismissEvent)
return animatorSet
}
/**
- * Returns a [PendingAnimation] to animate in the chip to instruct a user to select a second
- * app for splitscreen
+ * Returns a [PendingAnimation] to animate in the chip to instruct a user to select a second app
+ * for splitscreen
*/
- fun getShowSplitInstructionsAnim(launcher: StatefulActivity<*>) : PendingAnimation {
- safeRemoveViewFromDragLayer(launcher, splitSelectStateController.splitInstructionsView)
- val splitInstructionsView = SplitInstructionsView.getSplitInstructionsView(launcher)
+ fun getShowSplitInstructionsAnim(container: RecentsViewContainer): PendingAnimation {
+ safeRemoveViewFromDragLayer(container, splitSelectStateController.splitInstructionsView)
+ val splitInstructionsView = SplitInstructionsView.getSplitInstructionsView(container)
splitSelectStateController.splitInstructionsView = splitInstructionsView
- val timings = AnimUtils.getDeviceOverviewToSplitTimings(launcher.deviceProfile.isTablet)
+ val timings = AnimUtils.getDeviceOverviewToSplitTimings(container.deviceProfile.isTablet)
val anim = PendingAnimation(100 /*duration */)
splitInstructionsView.alpha = 0f
- anim.setViewAlpha(splitInstructionsView, 1f,
- Interpolators.clampToProgress(Interpolators.LINEAR,
- timings.instructionsContainerFadeInStartOffset,
- timings.instructionsContainerFadeInEndOffset))
- anim.addFloat(splitInstructionsView, SplitInstructionsView.UNFOLD, 0.1f, 1f,
- Interpolators.clampToProgress(Interpolators.EMPHASIZED_DECELERATE,
- timings.instructionsUnfoldStartOffset,
- timings.instructionsUnfoldEndOffset))
+ anim.setViewAlpha(
+ splitInstructionsView,
+ 1f,
+ Interpolators.clampToProgress(
+ Interpolators.LINEAR,
+ timings.instructionsContainerFadeInStartOffset,
+ timings.instructionsContainerFadeInEndOffset
+ )
+ )
+ anim.addFloat(
+ splitInstructionsView,
+ SplitInstructionsView.UNFOLD,
+ 0.1f,
+ 1f,
+ Interpolators.clampToProgress(
+ Interpolators.EMPHASIZED_DECELERATE,
+ timings.instructionsUnfoldStartOffset,
+ timings.instructionsUnfoldEndOffset
+ )
+ )
return anim
}
/** Removes the split instructions view from [launcher] drag layer. */
- fun removeSplitInstructionsView(launcher: StatefulActivity<*>) {
- safeRemoveViewFromDragLayer(launcher, splitSelectStateController.splitInstructionsView)
+ fun removeSplitInstructionsView(container: RecentsViewContainer) {
+ safeRemoveViewFromDragLayer(container, splitSelectStateController.splitInstructionsView)
}
/**
* Animates the first placeholder view to fullscreen and launches its task.
+ *
* TODO(b/276361926): Remove the [resetCallback] option once contextual launches
*/
- fun playAnimPlaceholderToFullscreen(launcher: StatefulActivity<*>, view: View,
- resetCallback: Optional<Runnable>) {
+ fun playAnimPlaceholderToFullscreen(
+ container: RecentsViewContainer,
+ view: View,
+ resetCallback: Optional<Runnable>
+ ) {
val stagedTaskView = view as FloatingTaskView
- val isTablet: Boolean = launcher.deviceProfile.isTablet
- val duration = if (isTablet) SplitAnimationTimings.TABLET_CONFIRM_DURATION else
- SplitAnimationTimings.PHONE_CONFIRM_DURATION
+ val isTablet: Boolean = container.deviceProfile.isTablet
+ val duration =
+ if (isTablet) SplitAnimationTimings.TABLET_CONFIRM_DURATION
+ else SplitAnimationTimings.PHONE_CONFIRM_DURATION
+
val pendingAnimation = PendingAnimation(duration.toLong())
val firstTaskStartingBounds = Rect()
val firstTaskEndingBounds = Rect()
stagedTaskView.getBoundsOnScreen(firstTaskStartingBounds)
- launcher.dragLayer.getBoundsOnScreen(firstTaskEndingBounds)
+ container.dragLayer.getBoundsOnScreen(firstTaskEndingBounds)
splitSelectStateController.setLaunchingFirstAppFullscreen()
stagedTaskView.addConfirmAnimation(
- pendingAnimation,
- RectF(firstTaskStartingBounds),
- firstTaskEndingBounds,
- false /* fadeWithThumbnail */,
- true /* isStagedTask */)
+ pendingAnimation,
+ RectF(firstTaskStartingBounds),
+ firstTaskEndingBounds,
+ false /* fadeWithThumbnail */,
+ true /* isStagedTask */
+ )
pendingAnimation.addEndListener {
splitSelectStateController.launchInitialAppFullscreen {
@@ -373,7 +491,7 @@
apps: Array<RemoteAnimationTarget>?,
wallpapers: Array<RemoteAnimationTarget>?,
nonApps: Array<RemoteAnimationTarget>?,
- stateManager: StateManager<*>,
+ stateManager: StateManager<*, *>,
depthController: DepthController?,
info: TransitionInfo?,
t: Transaction?,
@@ -421,8 +539,19 @@
check(info != null && t != null) {
"trying to launch an app pair icon, but encountered an unexpected null"
}
-
- composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
+ val appPairLaunchingAppIndex = hasChangesForBothAppPairs(launchingIconView, info)
+ if (appPairLaunchingAppIndex == -1) {
+ // Launch split app pair animation
+ composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
+ } else {
+ composeFullscreenIconSplitLaunchAnimator(
+ launchingIconView,
+ info,
+ t,
+ finishCallback,
+ appPairLaunchingAppIndex
+ )
+ }
} else {
// Fallback case: simple fade-in animation
check(info != null && t != null) {
@@ -441,7 +570,7 @@
@VisibleForTesting
fun composeRecentsSplitLaunchAnimator(
launchingTaskView: GroupedTaskView,
- stateManager: StateManager<*>,
+ stateManager: StateManager<*, *>,
depthController: DepthController?,
info: TransitionInfo,
t: Transaction,
@@ -469,7 +598,7 @@
apps: Array<RemoteAnimationTarget>,
wallpapers: Array<RemoteAnimationTarget>,
nonApps: Array<RemoteAnimationTarget>,
- stateManager: StateManager<*>,
+ stateManager: StateManager<*, *>,
depthController: DepthController?,
finishCallback: Runnable
) {
@@ -487,11 +616,48 @@
}
/**
+ * @return -1 if [transitionInfo] contains both apps of the app pair to be animated, otherwise
+ * the integer index corresponding to [launchingIconView]'s contents for the single app to be
+ * animated
+ */
+ fun hasChangesForBothAppPairs(
+ launchingIconView: AppPairIcon,
+ transitionInfo: TransitionInfo
+ ): Int {
+ val intent1 = launchingIconView.info.getFirstApp().intent.component?.packageName
+ val intent2 = launchingIconView.info.getSecondApp().intent.component?.packageName
+ var launchFullscreenAppIndex = -1
+ for (change in transitionInfo.changes) {
+ val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
+ if (
+ TransitionUtil.isOpeningType(change.mode) &&
+ taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN
+ ) {
+ val baseIntent = taskInfo.baseIntent.component?.packageName
+ if (baseIntent == intent1) {
+ if (launchFullscreenAppIndex > -1) {
+ launchFullscreenAppIndex = -1
+ break
+ }
+ launchFullscreenAppIndex = 0
+ } else if (baseIntent == intent2) {
+ if (launchFullscreenAppIndex > -1) {
+ launchFullscreenAppIndex = -1
+ break
+ }
+ launchFullscreenAppIndex = 1
+ }
+ }
+ }
+ return launchFullscreenAppIndex
+ }
+
+ /**
* When the user taps an app pair icon to launch split, this will play the tasks' launch
* animation from the position of the icon.
*
- * To find the root shell leash that we want to fade in, we do the following:
- * The Changes we receive in transitionInfo are structured like this
+ * To find the root shell leash that we want to fade in, we do the following: The Changes we
+ * receive in transitionInfo are structured like this
*
* Root (grandparent)
* |
@@ -503,9 +669,9 @@
* |
* --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
*
- * We want to animate the Root (grandparent) so that it affects both apps and the divider.
- * To do this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the
- * left-side ones, for simplicity) and traverse the tree until we find the grandparent.
+ * We want to animate the Root (grandparent) so that it affects both apps and the divider. To do
+ * this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the left-side ones,
+ * for simplicity) and traverse the tree until we find the grandparent.
*
* This function is only called when we are animating the app pair in from scratch. It is NOT
* called when we are animating in from an existing visible TaskView tile or an app that is
@@ -521,21 +687,21 @@
// If launching an app pair from Taskbar inside of an app context (no access to Launcher),
// use the scale-up animation
if (launchingIconView.context is TaskbarActivityContext) {
- composeScaleUpLaunchAnimation(transitionInfo, t, finishCallback)
+ composeScaleUpLaunchAnimation(
+ transitionInfo,
+ t,
+ finishCallback,
+ WINDOWING_MODE_MULTI_WINDOW
+ )
return
}
// Else we are in Launcher and can launch with the full icon stretch-and-split animation.
- val launcher = Launcher.getLauncher(launchingIconView.context)
+ val launcher = QuickstepLauncher.getLauncher(launchingIconView.context)
val dp = launcher.deviceProfile
// Create an AnimatorSet that will run both shell and launcher transitions together
val launchAnimation = AnimatorSet()
- val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
- val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet)
- progressUpdater.setDuration(timings.getDuration().toLong())
- progressUpdater.interpolator = Interpolators.LINEAR
-
var rootCandidate: Change? = null
for (change in transitionInfo.changes) {
@@ -544,8 +710,10 @@
// TODO (b/316490565): Replace this logic when SplitBounds is available to
// startAnimation() and we can know the precise taskIds of launching tasks.
// Find a change that has WINDOWING_MODE_MULTI_WINDOW.
- if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
- (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)) {
+ if (
+ taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
+ (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
+ ) {
// Check if it is a left/top app.
val isLeftTopApp =
(dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
@@ -577,6 +745,122 @@
// Make sure nothing weird happened, like getChange() returning null.
check(rootCandidate != null) { "Failed to find a root leash" }
+ // Create a new floating view in Launcher, positioned above the launching icon
+ val drawableArea = launchingIconView.iconDrawableArea
+ val appIcon1 = launchingIconView.info.getFirstApp().newIcon(launchingIconView.context)
+ val appIcon2 = launchingIconView.info.getSecondApp().newIcon(launchingIconView.context)
+ appIcon1.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
+ appIcon2.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
+
+ val floatingView =
+ FloatingAppPairView.getFloatingAppPairView(
+ launcher,
+ drawableArea,
+ appIcon1,
+ appIcon2,
+ dividerPos
+ )
+ floatingView.bringToFront()
+
+ launchAnimation.play(
+ getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView, rootCandidate)
+ )
+ launchAnimation.start()
+ }
+
+ /**
+ * Similar to [composeIconSplitLaunchAnimator], but instructs [FloatingAppPairView] to animate a
+ * single fullscreen icon + background instead of for a pair
+ */
+ @VisibleForTesting
+ fun composeFullscreenIconSplitLaunchAnimator(
+ launchingIconView: AppPairIcon,
+ transitionInfo: TransitionInfo,
+ t: Transaction,
+ finishCallback: Runnable,
+ launchFullscreenIndex: Int
+ ) {
+ // If launching an app pair from Taskbar inside of an app context (no access to Launcher),
+ // use the scale-up animation
+ if (launchingIconView.context is TaskbarActivityContext) {
+ composeScaleUpLaunchAnimation(
+ transitionInfo,
+ t,
+ finishCallback,
+ WINDOWING_MODE_FULLSCREEN
+ )
+ return
+ }
+
+ // Else we are in Launcher and can launch with the full icon stretch-and-split animation.
+ val launcher = QuickstepLauncher.getLauncher(launchingIconView.context)
+ val dp = launcher.deviceProfile
+
+ // Create an AnimatorSet that will run both shell and launcher transitions together
+ val launchAnimation = AnimatorSet()
+
+ val appInfo =
+ launchingIconView.info.getContents()[launchFullscreenIndex] as WorkspaceItemInfo
+ val intentToLaunch = appInfo.intent.component?.packageName
+ var rootCandidate: Change? = null
+ for (change in transitionInfo.changes) {
+ val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
+ val baseIntent = taskInfo.baseIntent.component?.packageName
+ if (
+ TransitionUtil.isOpeningType(change.mode) &&
+ taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN &&
+ baseIntent == intentToLaunch
+ ) {
+ rootCandidate = change
+ }
+ }
+
+ // If we could not find a proper root candidate, something went wrong.
+ check(rootCandidate != null) { "Could not find a split root candidate" }
+
+ // Recurse up the tree until parent is null, then we've found our root.
+ var parentToken: WindowContainerToken? = rootCandidate.parent
+ while (parentToken != null) {
+ rootCandidate = transitionInfo.getChange(parentToken) ?: break
+ parentToken = rootCandidate.parent
+ }
+
+ // Make sure nothing weird happened, like getChange() returning null.
+ check(rootCandidate != null) { "Failed to find a root leash" }
+
+ // Create a new floating view in Launcher, positioned above the launching icon
+ val drawableArea = launchingIconView.iconDrawableArea
+ val appIcon = appInfo.newIcon(launchingIconView.context)
+ appIcon.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
+
+ val floatingView =
+ FloatingAppPairView.getFloatingAppPairView(
+ launcher,
+ drawableArea,
+ appIcon,
+ null /*appIcon2*/,
+ 0 /*dividerPos*/
+ )
+ floatingView.bringToFront()
+ launchAnimation.play(
+ getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView, rootCandidate)
+ )
+ launchAnimation.start()
+ }
+
+ private fun getIconLaunchValueAnimator(
+ t: Transaction,
+ dp: com.android.launcher3.DeviceProfile,
+ finishCallback: Runnable,
+ launcher: QuickstepLauncher,
+ floatingView: FloatingAppPairView,
+ rootCandidate: Change
+ ): ValueAnimator {
+ val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
+ val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet)
+ progressUpdater.setDuration(timings.getDuration().toLong())
+ progressUpdater.interpolator = Interpolators.LINEAR
+
// Shell animation: the apps are revealed toward end of the launch animation
progressUpdater.addUpdateListener { valueAnimator: ValueAnimator ->
val progress =
@@ -592,30 +876,12 @@
t.apply()
}
- // Create a new floating view in Launcher, positioned above the launching icon
- val drawableArea = launchingIconView.iconDrawableArea
- val appIcon1 = launchingIconView.info.contents[0].newIcon(launchingIconView.context)
- val appIcon2 = launchingIconView.info.contents[1].newIcon(launchingIconView.context)
- appIcon1.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
- appIcon2.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
- val floatingView =
- FloatingAppPairView.getFloatingAppPairView(
- launcher,
- drawableArea,
- appIcon1,
- appIcon2,
- dividerPos
- )
-
- // Launcher animation: animate the floating view, expanding to fill the display surface
progressUpdater.addUpdateListener(
object : MultiValueUpdateListener() {
var mDx =
FloatProp(
floatingView.startingPosition.left,
dp.widthPx / 2f - floatingView.startingPosition.width() / 2f,
- 0f /* delay */,
- timings.getDuration().toFloat(),
Interpolators.clampToProgress(
timings.getStagedRectXInterpolator(),
timings.stagedRectSlideStartOffset,
@@ -626,8 +892,6 @@
FloatProp(
floatingView.startingPosition.top,
dp.heightPx / 2f - floatingView.startingPosition.height() / 2f,
- 0f /* delay */,
- timings.getDuration().toFloat(),
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
@@ -638,8 +902,6 @@
FloatProp(
1f /* start */,
dp.widthPx / floatingView.startingPosition.width(),
- 0f /* delay */,
- timings.getDuration().toFloat(),
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
@@ -650,8 +912,6 @@
FloatProp(
1f /* start */,
dp.heightPx / floatingView.startingPosition.height(),
- 0f /* delay */,
- timings.getDuration().toFloat(),
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
@@ -669,8 +929,6 @@
}
}
)
-
- // When animation ends, remove the floating view and run finishCallback
progressUpdater.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
@@ -680,19 +938,20 @@
}
)
- launchAnimation.play(progressUpdater)
- launchAnimation.start()
+ return progressUpdater
}
/**
* This is a scale-up-and-fade-in animation (34% to 100%) for launching an app in Overview when
- * there is no visible associated tile to expand from.
+ * there is no visible associated tile to expand from. [windowingMode] helps determine whether
+ * we are looking for a split or a single fullscreen [Change]
*/
@VisibleForTesting
fun composeScaleUpLaunchAnimation(
transitionInfo: TransitionInfo,
t: Transaction,
- finishCallback: Runnable
+ finishCallback: Runnable,
+ windowingMode: Int
) {
val launchAnimation = AnimatorSet()
val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
@@ -706,9 +965,8 @@
// TODO (b/316490565): Replace this logic when SplitBounds is available to
// startAnimation() and we can know the precise taskIds of launching tasks.
- // Find a change that has WINDOWING_MODE_MULTI_WINDOW.
if (
- taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
+ taskInfo.windowingMode == windowingMode &&
(change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
) {
// Found one!
@@ -822,11 +1080,30 @@
}
}
+ if (splitRoot1 != null) {
+ // Set the highest level split root alpha; we could technically use the parent of
+ // either splitRoot1 or splitRoot2
+ val parentToken = splitRoot1.parent
+ var rootLayer: Change? = null
+ if (parentToken != null) {
+ rootLayer = transitionInfo.getChange(parentToken)
+ }
+ if (rootLayer != null && rootLayer.leash != null) {
+ openingTargets.add(rootLayer.leash)
+ }
+ }
+
val animTransaction = Transaction()
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.setDuration(QuickstepTransitionManager.SPLIT_LAUNCH_DURATION.toLong())
animator.addUpdateListener { valueAnimator: ValueAnimator ->
- val progress = valueAnimator.animatedFraction
+ val progress =
+ Interpolators.clampToProgress(
+ Interpolators.LINEAR,
+ valueAnimator.animatedFraction,
+ 0.8f,
+ 1f
+ )
for (leash in openingTargets) {
animTransaction.setAlpha(leash, progress)
}
@@ -848,26 +1125,13 @@
}
)
- if (splitRoot1 != null) {
- // Set the highest level split root alpha; we could technically use the parent of
- // either splitRoot1 or splitRoot2
- val parentToken = splitRoot1.parent
- var rootLayer: Change? = null
- if (parentToken != null) {
- rootLayer = transitionInfo.getChange(parentToken)
- }
- if (rootLayer != null && rootLayer.leash != null) {
- t.setAlpha(rootLayer.leash, 1f)
- }
- }
-
t.apply()
animator.start()
}
- private fun safeRemoveViewFromDragLayer(launcher: StatefulActivity<*>, view: View?) {
+ private fun safeRemoveViewFromDragLayer(container: RecentsViewContainer, view: View?) {
if (view != null) {
- launcher.dragLayer.removeView(view)
+ container.dragLayer.removeView(view)
}
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
index 06edb14..8258ab8 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
@@ -38,36 +38,40 @@
import java.io.PrintWriter
/**
- * Holds/transforms/signs/seals/delivers information for the transient state of the user
- * selecting a first app to start split with and then choosing a second app.
- * This class DOES NOT associate itself with drag-and-drop split screen starts because they come
- * from the bad part of town.
+ * Holds/transforms/signs/seals/delivers information for the transient state of the user selecting a
+ * first app to start split with and then choosing a second app. This class DOES NOT associate
+ * itself with drag-and-drop split screen starts because they come from the bad part of town.
*
* After setting the correct fields for initial/second.* variables, this converts them into the
- * correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary
- * data back via [getSplitLaunchData]. Note: there should be only one "initial" field and one
- * "second" field set, with the rest remaining null. (Exception: [Intent] and [UserHandle] are
- * always passed in together as a set, and are converted to a single [PendingIntent] or
+ * correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary data
+ * back via [getSplitLaunchData]. Note: there should be only one "initial" field and one "second"
+ * field set, with the rest remaining null. (Exception: [Intent] and [UserHandle] are always passed
+ * in together as a set, and are converted to a single [PendingIntent] or
* [ShortcutInfo]+[PendingIntent] before launch.)
*
* [SplitLaunchType] indicates the type of tasks/apps/intents being launched given the provided
* state
*/
-class SplitSelectDataHolder(
- var context: Context?
-) {
+class SplitSelectDataHolder(var context: Context?) {
val TAG = SplitSelectDataHolder::class.simpleName
/**
- * Order of the constant indicates the order of which task/app was selected.
- * Ex. SPLIT_TASK_SHORTCUT means primary split app identified by task, secondary is shortcut
+ * Order of the constant indicates the order of which task/app was selected. Ex.
+ * SPLIT_TASK_SHORTCUT means primary split app identified by task, secondary is shortcut
* SPLIT_SHORTCUT_TASK means primary split app is determined by shortcut, secondary is task
*/
companion object {
- @IntDef(SPLIT_TASK_TASK, SPLIT_TASK_PENDINGINTENT, SPLIT_TASK_SHORTCUT,
- SPLIT_PENDINGINTENT_TASK, SPLIT_PENDINGINTENT_PENDINGINTENT, SPLIT_SHORTCUT_TASK,
- SPLIT_SINGLE_TASK_FULLSCREEN, SPLIT_SINGLE_INTENT_FULLSCREEN,
- SPLIT_SINGLE_SHORTCUT_FULLSCREEN)
+ @IntDef(
+ SPLIT_TASK_TASK,
+ SPLIT_TASK_PENDINGINTENT,
+ SPLIT_TASK_SHORTCUT,
+ SPLIT_PENDINGINTENT_TASK,
+ SPLIT_PENDINGINTENT_PENDINGINTENT,
+ SPLIT_SHORTCUT_TASK,
+ SPLIT_SINGLE_TASK_FULLSCREEN,
+ SPLIT_SINGLE_INTENT_FULLSCREEN,
+ SPLIT_SINGLE_SHORTCUT_FULLSCREEN
+ )
@Retention(AnnotationRetention.SOURCE)
annotation class SplitLaunchType
@@ -84,8 +88,7 @@
const val SPLIT_SINGLE_SHORTCUT_FULLSCREEN = 8
}
- @StagePosition
- private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED
+ @StagePosition private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED
private var itemInfo: ItemInfo? = null
private var secondItemInfo: ItemInfo? = null
private var splitEvent: EventEnum? = null
@@ -108,12 +111,16 @@
/**
* @param alreadyRunningTask if set to [android.app.ActivityTaskManager.INVALID_TASK_ID]
- * then @param intent will be used to launch the initial task
+ * then @param intent will be used to launch the initial task
* @param intent will be ignored if @param alreadyRunningTask is set
*/
- fun setInitialTaskSelect(intent: Intent?, @StagePosition stagePosition: Int,
- itemInfo: ItemInfo?, splitEvent: EventEnum?,
- alreadyRunningTask: Int) {
+ fun setInitialTaskSelect(
+ intent: Intent?,
+ @StagePosition stagePosition: Int,
+ itemInfo: ItemInfo?,
+ splitEvent: EventEnum?,
+ alreadyRunningTask: Int
+ ) {
if (alreadyRunningTask != INVALID_TASK_ID) {
initialTaskId = alreadyRunningTask
} else {
@@ -127,15 +134,21 @@
* To be called after first task selected from using a split shortcut from the fullscreen
* running app.
*/
- fun setInitialTaskSelect(info: RunningTaskInfo,
- @StagePosition stagePosition: Int, itemInfo: ItemInfo?,
- splitEvent: EventEnum?) {
+ fun setInitialTaskSelect(
+ info: RunningTaskInfo,
+ @StagePosition stagePosition: Int,
+ itemInfo: ItemInfo?,
+ splitEvent: EventEnum?
+ ) {
initialTaskId = info.taskId
setInitialData(stagePosition, splitEvent, itemInfo)
}
- private fun setInitialData(@StagePosition stagePosition: Int,
- event: EventEnum?, item: ItemInfo?) {
+ private fun setInitialData(
+ @StagePosition stagePosition: Int,
+ event: EventEnum?,
+ item: ItemInfo?
+ ) {
itemInfo = item
initialStagePosition = stagePosition
splitEvent = event
@@ -143,6 +156,7 @@
/**
* To be called as soon as user selects the second task (even if animations aren't complete)
+ *
* @param taskId The second task that will be launched.
*/
fun setSecondTask(taskId: Int, itemInfo: ItemInfo) {
@@ -152,6 +166,7 @@
/**
* To be called as soon as user selects the second app (even if animations aren't complete)
+ *
* @param intent The second intent that will be launched.
* @param user The user of that intent.
*/
@@ -162,8 +177,9 @@
}
/**
- * To be called as soon as user selects the second app (even if animations aren't complete)
- * Sets [secondUser] from that of the pendingIntent
+ * To be called as soon as user selects the second app (even if animations aren't complete) Sets
+ * [secondUser] from that of the pendingIntent
+ *
* @param pendingIntent The second PendingIntent that will be launched.
*/
fun setSecondTask(pendingIntent: PendingIntent, itemInfo: ItemInfo) {
@@ -173,9 +189,9 @@
}
/**
- * Similar to [setSecondTask] except this is to be called for widgets which can pass through
- * an extra intent from their RemoteResponse.
- * See [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first
+ * Similar to [setSecondTask] except this is to be called for widgets which can pass through an
+ * extra intent from their RemoteResponse. See
+ * [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first
*/
fun setSecondWidget(pendingIntent: PendingIntent, widgetIntent: Intent?, itemInfo: ItemInfo) {
setSecondTask(pendingIntent, itemInfo)
@@ -184,8 +200,7 @@
private fun getShortcutInfo(intent: Intent?, user: UserHandle?): ShortcutInfo? {
val intentPackage = intent?.getPackage() ?: return null
- val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID)
- ?: return null
+ val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) ?: return null
try {
val context: Context =
if (user != null) {
@@ -200,9 +215,7 @@
return null
}
- /**
- * Converts intents to pendingIntents, associating the [user] with the intent if provided
- */
+ /** Converts intents to pendingIntents, associating the [user] with the intent if provided */
private fun getPendingIntent(intent: Intent?, user: UserHandle?): PendingIntent? {
if (intent != initialIntent && intent != secondIntent) {
throw IllegalStateException("Invalid intent to convert to PendingIntent")
@@ -211,12 +224,21 @@
return if (intent == null) {
null
} else if (user != null) {
- PendingIntent.getActivityAsUser(context, 0, intent,
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
- null /* options */, user)
+ PendingIntent.getActivityAsUser(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
+ null /* options */,
+ user
+ )
} else {
- PendingIntent.getActivity(context, 0, intent,
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT)
+ PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
+ )
}
}
@@ -224,7 +246,7 @@
* @return [SplitLaunchData] with the necessary fields populated as determined by
* [SplitLaunchData.splitLaunchType]. This is to be used for launching splitscreen
*/
- fun getSplitLaunchData() : SplitLaunchData {
+ fun getSplitLaunchData(): SplitLaunchData {
// Convert all intents to shortcut infos to see if determine if we launch shortcut or intent
convertIntentsToFinalTypes()
val splitLaunchType = getSplitLaunchType()
@@ -241,7 +263,7 @@
* [SplitLaunchData.splitLaunchType]. This is to be used for launching an initially selected
* split task in fullscreen
*/
- fun getFullscreenLaunchData() : SplitLaunchData {
+ fun getFullscreenLaunchData(): SplitLaunchData {
// Convert all intents to shortcut infos to determine if we launch shortcut or intent
convertIntentsToFinalTypes()
val splitLaunchType = getFullscreenLaunchType()
@@ -249,21 +271,22 @@
return generateSplitLaunchData(splitLaunchType)
}
- private fun generateSplitLaunchData(@SplitLaunchType splitLaunchType: Int) : SplitLaunchData {
+ private fun generateSplitLaunchData(@SplitLaunchType splitLaunchType: Int): SplitLaunchData {
return SplitLaunchData(
- splitLaunchType,
- initialTaskId,
- secondTaskId,
- initialPendingIntent,
- secondPendingIntent,
- widgetSecondIntent,
- initialUser?.identifier ?: -1,
- secondUser?.identifier ?: -1,
- initialShortcut,
- secondShortcut,
- itemInfo,
- splitEvent,
- initialStagePosition)
+ splitLaunchType,
+ initialTaskId,
+ secondTaskId,
+ initialPendingIntent,
+ secondPendingIntent,
+ widgetSecondIntent,
+ initialUser?.identifier ?: -1,
+ secondUser?.identifier ?: -1,
+ initialShortcut,
+ secondShortcut,
+ itemInfo,
+ splitEvent,
+ initialStagePosition
+ )
}
/**
@@ -273,8 +296,7 @@
* Note that both [initialIntent] and [secondIntent] will be nullified on method return
*
* One caveat is that if [secondPendingIntent] is set, we will use that and *not* attempt to
- * convert [secondIntent].
- * This also leaves [widgetSecondIntent] untouched.
+ * convert [secondIntent]. This also leaves [widgetSecondIntent] untouched.
*/
private fun convertIntentsToFinalTypes() {
initialShortcut = getShortcutInfo(initialIntent, initialUser)
@@ -297,8 +319,8 @@
}
/**
- * Only valid data fields at this point should be tasks, shortcuts, or pendingIntents
- * Intents need to be converted in [convertIntentsToFinalTypes] prior to calling this method
+ * Only valid data fields at this point should be tasks, shortcuts, or pendingIntents Intents
+ * need to be converted in [convertIntentsToFinalTypes] prior to calling this method
*/
@VisibleForTesting
@SplitLaunchType
@@ -354,41 +376,40 @@
}
data class SplitLaunchData(
- @SplitLaunchType
- val splitLaunchType: Int,
- var initialTaskId: Int = INVALID_TASK_ID,
- var secondTaskId: Int = INVALID_TASK_ID,
- var initialPendingIntent: PendingIntent? = null,
- var secondPendingIntent: PendingIntent? = null,
- var widgetSecondIntent: Intent? = null,
- var initialUserId: Int = -1,
- var secondUserId: Int = -1,
- var initialShortcut: ShortcutInfo? = null,
- var secondShortcut: ShortcutInfo? = null,
- var itemInfo: ItemInfo? = null,
- var splitEvent: EventEnum? = null,
- val initialStagePosition: Int = STAGE_POSITION_UNDEFINED
+ @SplitLaunchType val splitLaunchType: Int,
+ var initialTaskId: Int = INVALID_TASK_ID,
+ var secondTaskId: Int = INVALID_TASK_ID,
+ var initialPendingIntent: PendingIntent? = null,
+ var secondPendingIntent: PendingIntent? = null,
+ var widgetSecondIntent: Intent? = null,
+ var initialUserId: Int = -1,
+ var secondUserId: Int = -1,
+ var initialShortcut: ShortcutInfo? = null,
+ var secondShortcut: ShortcutInfo? = null,
+ var itemInfo: ItemInfo? = null,
+ var splitEvent: EventEnum? = null,
+ val initialStagePosition: Int = STAGE_POSITION_UNDEFINED
)
/**
- * @return `true` if first task has been selected and waiting for the second task to be
- * chosen
+ * @return `true` if first task has been selected and waiting for the second task to be chosen
*/
fun isSplitSelectActive(): Boolean {
return isInitialTaskIntentSet() && !isSecondTaskIntentSet()
}
/**
- * @return `true` if the first and second task have been chosen and split is waiting to
- * be launched
+ * @return `true` if the first and second task have been chosen and split is waiting to be
+ * launched
*/
fun isBothSplitAppsConfirmed(): Boolean {
return isInitialTaskIntentSet() && isSecondTaskIntentSet()
}
private fun isInitialTaskIntentSet(): Boolean {
- return initialTaskId != INVALID_TASK_ID || initialIntent != null ||
- initialPendingIntent != null
+ return initialTaskId != INVALID_TASK_ID ||
+ initialIntent != null ||
+ initialPendingIntent != null
}
fun getInitialTaskId(): Int {
@@ -416,8 +437,9 @@
}
private fun isSecondTaskIntentSet(): Boolean {
- return secondTaskId != INVALID_TASK_ID || secondIntent != null
- || secondPendingIntent != null
+ return secondTaskId != INVALID_TASK_ID ||
+ secondIntent != null ||
+ secondPendingIntent != null
}
fun resetState() {
@@ -437,7 +459,7 @@
}
fun dump(prefix: String, writer: PrintWriter) {
- writer.println("$prefix ${javaClass.simpleName}")
+ writer.println("$prefix SplitSelectDataHolder")
writer.println("$prefix\tinitialStagePosition= $initialStagePosition")
writer.println("$prefix\tinitialTaskId= $initialTaskId")
writer.println("$prefix\tsecondTaskId= $secondTaskId")
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 8e2520e..7e7c794 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -35,7 +35,6 @@
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
@@ -64,15 +63,15 @@
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.RemoteTransition;
+import android.window.RemoteTransitionStub;
import android.window.TransitionInfo;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.apppairs.AppPairIcon;
@@ -82,16 +81,15 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.BackPressHandler;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationController;
-import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SplitSelectionListener;
@@ -100,10 +98,12 @@
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.SplitInstructionsView;
import com.android.systemui.animation.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.splitscreen.ISplitSelectListener;
@@ -120,7 +120,7 @@
public class SplitSelectStateController {
private static final String TAG = "SplitSelectStateCtor";
- private StatefulActivity mContext;
+ private RecentsViewContainer mContainer;
private final Handler mHandler;
private final RecentsModel mRecentTasksModel;
@Nullable
@@ -131,6 +131,7 @@
private final StatsLogManager mStatsLogManager;
private final SystemUiProxy mSystemUiProxy;
private final StateManager mStateManager;
+ @Nullable
private SplitFromDesktopController mSplitFromDesktopController;
@Nullable
private DepthController mDepthController;
@@ -151,6 +152,12 @@
/** True when the first selected split app is being launched in fullscreen. */
private boolean mLaunchingFirstAppFullscreen;
+ /**
+ * Should be a constant from {@link com.android.internal.jank.Cuj} or -1, does not need to be
+ * set for all launches.
+ */
+ private int mLaunchCuj = -1;
+
private FloatingTaskView mFirstFloatingTaskView;
private SplitInstructionsView mSplitInstructionsView;
@@ -171,7 +178,7 @@
public void onBackInvoked() {
// When exiting from split selection, leave current context to go to
// homescreen as well
- getSplitAnimationController().playPlaceholderDismissAnim(mContext,
+ getSplitAnimationController().playPlaceholderDismissAnim(mContainer,
LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
if (mActivityBackCallback != null) {
mActivityBackCallback.run();
@@ -179,11 +186,11 @@
}
};
- public SplitSelectStateController(StatefulActivity context, Handler handler,
+ public SplitSelectStateController(RecentsViewContainer container, Handler handler,
StateManager stateManager, DepthController depthController,
StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel,
Runnable activityBackCallback) {
- mContext = context;
+ mContainer = container;
mHandler = handler;
mStatsLogManager = statsLogManager;
mSystemUiProxy = systemUiProxy;
@@ -192,15 +199,18 @@
mRecentTasksModel = recentsModel;
mActivityBackCallback = activityBackCallback;
mSplitAnimationController = new SplitAnimationController(this);
- mAppPairsController = new AppPairsController(context, this, statsLogManager);
- mSplitSelectDataHolder = new SplitSelectDataHolder(mContext);
+ mAppPairsController = new AppPairsController(mContainer.asContext(), this, statsLogManager);
+ mSplitSelectDataHolder = new SplitSelectDataHolder(mContainer.asContext());
}
public void onDestroy() {
- mContext = null;
+ mContainer = null;
mActivityBackCallback = null;
mAppPairsController.onDestroy();
mSplitSelectDataHolder.onDestroy();
+ if (mSplitFromDesktopController != null) {
+ mSplitFromDesktopController.onDestroy();
+ }
}
/**
@@ -635,8 +645,18 @@
}
}
- public void initSplitFromDesktopController(Launcher launcher) {
- mSplitFromDesktopController = new SplitFromDesktopController(launcher);
+ /**
+ * Init {@code SplitFromDesktopController}
+ */
+ public void initSplitFromDesktopController(QuickstepLauncher launcher,
+ OverviewComponentObserver overviewComponentObserver) {
+ initSplitFromDesktopController(
+ new SplitFromDesktopController(launcher, overviewComponentObserver));
+ }
+
+ @VisibleForTesting
+ void initSplitFromDesktopController(SplitFromDesktopController controller) {
+ mSplitFromDesktopController = controller;
}
private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId,
@@ -707,10 +727,14 @@
return mSplitAnimationController;
}
+ public void setLaunchingCuj(int launchCuj) {
+ mLaunchCuj = launchCuj;
+ }
+
/**
* Requires Shell Transitions
*/
- private class RemoteSplitLaunchTransitionRunner extends IRemoteTransition.Stub {
+ private class RemoteSplitLaunchTransitionRunner extends RemoteTransitionStub {
private final int mInitialTaskId;
private final int mSecondTaskId;
@@ -759,11 +783,6 @@
}
@Override
- public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishedCallback) { }
-
- @Override
public void onTransitionConsumed(IBinder transition, boolean aborted)
throws RemoteException {
MAIN_EXECUTOR.execute(() -> {
@@ -850,6 +869,11 @@
mSplitInstructionsView = null;
mLaunchingFirstAppFullscreen = false;
+ if (mLaunchCuj != -1) {
+ InteractionJankMonitorWrapper.end(mLaunchCuj);
+ }
+ mLaunchCuj = -1;
+
if (mSessionInstanceIds != null) {
mStatsLogManager.logger()
.withInstanceId(mSessionInstanceIds.second)
@@ -928,7 +952,7 @@
public class SplitFromDesktopController {
private static final String TAG = "SplitFromDesktopController";
- private final Launcher mLauncher;
+ private final QuickstepLauncher mLauncher;
private final OverviewComponentObserver mOverviewComponentObserver;
private final int mSplitPlaceholderSize;
private final int mSplitPlaceholderInset;
@@ -936,12 +960,10 @@
private ISplitSelectListener mSplitSelectListener;
private Drawable mAppIcon;
- public SplitFromDesktopController(Launcher launcher) {
+ public SplitFromDesktopController(QuickstepLauncher launcher,
+ OverviewComponentObserver overviewComponentObserver) {
mLauncher = launcher;
- RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(
- launcher.getApplicationContext());
- mOverviewComponentObserver =
- new OverviewComponentObserver(launcher.getApplicationContext(), deviceState);
+ mOverviewComponentObserver = overviewComponentObserver;
mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
R.dimen.split_placeholder_size);
mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize(
@@ -950,7 +972,6 @@
@Override
public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
int splitPosition, Rect taskBounds) {
- if (!isDesktopModeSupported()) return false;
MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition,
taskBounds));
return true;
@@ -959,6 +980,12 @@
SystemUiProxy.INSTANCE.get(mLauncher).registerSplitSelectListener(mSplitSelectListener);
}
+ void onDestroy() {
+ SystemUiProxy.INSTANCE.get(mLauncher).unregisterSplitSelectListener(
+ mSplitSelectListener);
+ mSplitSelectListener = null;
+ }
+
/**
* Enter split select from desktop mode.
* @param taskInfo the desktop task to move to split stage
diff --git a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
index d1ec2b6..7557ad1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
@@ -17,6 +17,7 @@
package com.android.quickstep.util;
import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.LINEAR;
import android.view.animation.Interpolator;
@@ -31,6 +32,8 @@
abstract public int getPlaceholderIconFadeInEnd();
abstract public int getStagedRectSlideStart();
abstract public int getStagedRectSlideEnd();
+ abstract public int getBackingScrimFadeInStart();
+ abstract public int getBackingScrimFadeInEnd();
// Common timings
public int getInstructionsFadeStart() { return 0; }
@@ -39,6 +42,7 @@
public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; }
public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; }
public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; }
+ public Interpolator getBackingScrimFadeInterpolator() { return LINEAR; }
abstract public int getDuration();
@@ -48,4 +52,10 @@
public float getInstructionsFadeEndOffset() {
return (float) getInstructionsFadeEnd() / getDuration();
}
+ public float getBackingScrimFadeInStartOffset() {
+ return (float) getBackingScrimFadeInStart() / getDuration();
+ }
+ public float getBackingScrimFadeInEndOffset() {
+ return (float) getBackingScrimFadeInEnd() / getDuration();
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 445a540..4962367 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -16,9 +16,7 @@
package com.android.quickstep.util;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -37,17 +35,17 @@
import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -55,16 +53,17 @@
/** Handles when the stage split lands on the home screen. */
public class SplitToWorkspaceController {
- private final Launcher mLauncher;
+ private final QuickstepLauncher mLauncher;
private final SplitSelectStateController mController;
private final int mHalfDividerSize;
private final IconCache mIconCache;
- public SplitToWorkspaceController(Launcher launcher, SplitSelectStateController controller) {
+ public SplitToWorkspaceController(QuickstepLauncher launcher,
+ SplitSelectStateController controller) {
mLauncher = launcher;
mController = controller;
- mIconCache = LauncherAppState.getInstanceNoCreate().getIconCache();
+ mIconCache = LauncherAppState.getInstance(launcher).getIconCache();
mHalfDividerSize = mLauncher.getResources().getDimensionPixelSize(
R.dimen.multi_window_task_divider_size) / 2;
}
@@ -126,7 +125,7 @@
intent = appInfo.intent;
user = appInfo.user;
bitmapInfo = appInfo.bitmap;
- } else if (tag instanceof FolderInfo fi && fi.itemType == ITEM_TYPE_APP_PAIR) {
+ } else if (tag instanceof AppPairInfo) {
// Prompt the user to select something else by wiggling the instructions view
mController.getSplitInstructionsView().goBoing();
return true;
@@ -164,6 +163,9 @@
new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
false /* fadeWithThumbnail */, true /* isStagedTask */);
+ View backingScrim = recentsView.getSplitSelectController().getSplitAnimationController()
+ .addScrimBehindAnim(pendingAnimation, mLauncher, view.getContext());
+
FloatingTaskView secondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mLauncher,
view, bitmap, icon, secondTaskStartingBounds);
secondFloatingTaskView.setAlpha(1);
@@ -174,6 +176,11 @@
private boolean mIsCancelled = false;
@Override
+ public void onAnimationStart(Animator animation) {
+ mController.launchSplitTasks(aBoolean -> cleanUp());
+ }
+
+ @Override
public void onAnimationCancel(Animator animation) {
mIsCancelled = true;
cleanUp();
@@ -182,7 +189,6 @@
@Override
public void onAnimationEnd(Animator animation) {
if (!mIsCancelled) {
- mController.launchSplitTasks(aBoolean -> cleanUp());
InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
}
}
@@ -190,6 +196,7 @@
private void cleanUp() {
mLauncher.getDragLayer().removeView(firstFloatingTaskView);
mLauncher.getDragLayer().removeView(secondFloatingTaskView);
+ mLauncher.getDragLayer().removeView(backingScrim);
mController.getSplitAnimationController().removeSplitInstructionsView(mLauncher);
mController.resetState();
}
@@ -198,8 +205,6 @@
}
private boolean shouldIgnoreSecondSplitLaunch() {
- return (!FeatureFlags.enableSplitContextually()
- && !isDesktopModeSupported())
- || !mController.isSplitSelectActive();
+ return !FeatureFlags.enableSplitContextually() || !mController.isSplitSelectActive();
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index 555bf21..5e42b90 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -62,12 +62,13 @@
private final int mSplitPlaceholderInset;
public SplitWithKeyboardShortcutController(QuickstepLauncher launcher,
- SplitSelectStateController controller) {
+ SplitSelectStateController controller,
+ OverviewComponentObserver overviewComponentObserver,
+ RecentsAnimationDeviceState deviceState) {
mLauncher = launcher;
mController = controller;
- mDeviceState = new RecentsAnimationDeviceState(launcher.getApplicationContext());
- mOverviewComponentObserver = new OverviewComponentObserver(launcher.getApplicationContext(),
- mDeviceState);
+ mDeviceState = deviceState;
+ mOverviewComponentObserver = overviewComponentObserver;
mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
R.dimen.split_placeholder_size);
@@ -135,7 +136,7 @@
RectF startingTaskRect = new RectF();
final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(
mLauncher, mLauncher.getDragLayer(),
- controller.screenshotTask(runningTaskInfo.taskId).thumbnail,
+ controller.screenshotTask(runningTaskInfo.taskId).getThumbnail(),
null /* icon */, startingTaskRect);
RecentsModel.INSTANCE.get(mLauncher.getApplicationContext())
.getIconCache()
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 6b3199f..997a842 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -38,7 +38,6 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Hotseat;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
@@ -65,7 +64,7 @@
// Should be used for animations running alongside this StaggeredWorkspaceAnim.
public static final int DURATION_MS = 250;
public static final int DURATION_TASKBAR_MS =
- QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
+ QuickstepTransitionManager.getTaskbarToHomeDuration();
private static final float MAX_VELOCITY_PX_PER_S = 22f;
@@ -75,13 +74,13 @@
private final AnimatorSet mAnimators = new AnimatorSet();
private final @Nullable View mIgnoredView;
- public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim,
- @Nullable View ignoredView) {
+ public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity,
+ boolean animateOverviewScrim, @Nullable View ignoredView) {
this(launcher, velocity, animateOverviewScrim, ignoredView, true);
}
- public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim,
- @Nullable View ignoredView, boolean staggerWorkspace) {
+ public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity,
+ boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace) {
prepareToAnimate(launcher, animateOverviewScrim);
mIgnoredView = ignoredView;
@@ -124,7 +123,8 @@
for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
View child = hotseatIcons.getChildAt(i);
CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
- addStaggeredAnimationForView(child, lp.getCellY() + 1, totalRows, duration);
+ addStaggeredAnimationForView(child, lp.getCellY() + 1,
+ totalRows, duration);
}
} else {
final int hotseatRow, qsbRow;
@@ -194,7 +194,8 @@
for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) {
View child = itemsContainer.getChildAt(i);
CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
- addStaggeredAnimationForView(child, lp.getCellY() + lp.cellVSpan, totalRows, duration);
+ addStaggeredAnimationForView(child, lp.getCellY() + lp.cellVSpan,
+ totalRows, duration);
}
mAnimators.addListener(new AnimatorListenerAdapter() {
@@ -209,7 +210,7 @@
/**
* Setup workspace with 0 duration to prepare for our staggered animation.
*/
- private void prepareToAnimate(Launcher launcher, boolean animateOverviewScrim) {
+ private void prepareToAnimate(QuickstepLauncher launcher, boolean animateOverviewScrim) {
StateAnimationConfig config = new StateAnimationConfig();
config.animFlags = SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER | SKIP_SCRIM;
config.duration = 0;
@@ -294,12 +295,10 @@
mAnimators.play(alpha);
}
- private void addDepthAnimationForState(Launcher launcher, LauncherState state, long duration) {
- if (!(launcher instanceof QuickstepLauncher)) {
- return;
- }
+ private void addDepthAnimationForState(QuickstepLauncher launcher, LauncherState state,
+ long duration) {
PendingAnimation builder = new PendingAnimation(duration);
- DepthController depthController = ((QuickstepLauncher) launcher).getDepthController();
+ DepthController depthController = launcher.getDepthController();
depthController.setStateWithAnimation(state, new StateAnimationConfig(), builder);
mAnimators.play(builder.buildAnim());
}
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 6c89be1..e44f148 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -46,10 +46,12 @@
* when swiping up (in gesture navigation mode).
*/
public class SwipePipToHomeAnimator extends RectFSpringAnim {
- private static final String TAG = SwipePipToHomeAnimator.class.getSimpleName();
+ private static final String TAG = "SwipePipToHomeAnimator";
private static final float END_PROGRESS = 1.0f;
+ private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f;
+
private final int mTaskId;
private final ActivityInfo mActivityInfo;
private final SurfaceControl mLeash;
@@ -112,7 +114,7 @@
@NonNull ActivityInfo activityInfo,
int appIconSizePx,
@NonNull SurfaceControl leash,
- @Nullable Rect sourceRectHint,
+ @NonNull Rect sourceRectHint,
@NonNull Rect appBounds,
@NonNull Matrix homeToWindowPositionMap,
@NonNull RectF startBounds,
@@ -135,24 +137,48 @@
mDestinationBoundsTransformed.set(destinationBoundsTransformed);
mSurfaceTransactionHelper = new PipSurfaceTransactionHelper(cornerRadius, shadowRadius);
- if (sourceRectHint != null && (sourceRectHint.width() < destinationBounds.width()
- || sourceRectHint.height() < destinationBounds.height())) {
+ final float aspectRatio = destinationBounds.width() / (float) destinationBounds.height();
+ String reasonForCreateOverlay = null; // For debugging purpose.
+ if (sourceRectHint.isEmpty()) {
+ reasonForCreateOverlay = "Source rect hint is empty";
+ } else if (sourceRectHint.width() < destinationBounds.width()
+ || sourceRectHint.height() < destinationBounds.height()) {
// This is a situation in which the source hint rect on at least one axis is smaller
// than the destination bounds, which presents a problem because we would have to scale
// up that axis to fit the bounds. So instead, just fallback to the non-source hint
// animation in this case.
- sourceRectHint = null;
- }
-
- if (sourceRectHint != null && !appBounds.contains(sourceRectHint)) {
+ reasonForCreateOverlay = "Source rect hint is too small " + sourceRectHint;
+ sourceRectHint.setEmpty();
+ } else if (!appBounds.contains(sourceRectHint)) {
// This is a situation in which the source hint rect is outside the app bounds, so it is
// not a valid rectangle to use for cropping app surface
- sourceRectHint = null;
+ reasonForCreateOverlay = "Source rect hint exceeds display bounds " + sourceRectHint;
+ sourceRectHint.setEmpty();
+ } else if (Math.abs(
+ aspectRatio - (sourceRectHint.width() / (float) sourceRectHint.height()))
+ > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+ // The source rect hint does not aspect ratio
+ reasonForCreateOverlay = "Source rect hint does not match aspect ratio "
+ + sourceRectHint + " aspect ratio " + aspectRatio;
+ sourceRectHint.setEmpty();
}
- if (sourceRectHint == null) {
- mSourceRectHint.setEmpty();
- mSourceHintRectInsets = null;
+ if (sourceRectHint.isEmpty()) {
+ // Crop a Rect matches the aspect ratio and pivots at the center point.
+ // To make the animation path simplified.
+ if ((appBounds.width() / (float) appBounds.height()) > aspectRatio) {
+ // use the full height.
+ mSourceRectHint.set(0, 0,
+ (int) (appBounds.height() * aspectRatio), appBounds.height());
+ mSourceRectHint.offset(
+ (appBounds.width() - mSourceRectHint.width()) / 2, 0);
+ } else {
+ // use the full width.
+ mSourceRectHint.set(0, 0,
+ appBounds.width(), (int) (appBounds.width() / aspectRatio));
+ mSourceRectHint.offset(
+ 0, (appBounds.height() - mSourceRectHint.height()) / 2);
+ }
// Create a new overlay layer. We do not call detach on this instance, it's propagated
// to other classes like PipTaskOrganizer / RecentsAnimationController to complete
@@ -162,13 +188,14 @@
new IconProvider(context).getIcon(mActivityInfo), appIconSizePx);
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
mPipContentOverlay.attach(tx, mLeash);
+ Log.d(TAG, getContentOverlay() + " is created: " + reasonForCreateOverlay);
} else {
mSourceRectHint.set(sourceRectHint);
- mSourceHintRectInsets = new Rect(sourceRectHint.left - appBounds.left,
- sourceRectHint.top - appBounds.top,
- appBounds.right - sourceRectHint.right,
- appBounds.bottom - sourceRectHint.bottom);
}
+ mSourceHintRectInsets = new Rect(mSourceRectHint.left - appBounds.left,
+ mSourceRectHint.top - appBounds.top,
+ appBounds.right - mSourceRectHint.right,
+ appBounds.bottom - mSourceRectHint.bottom);
addAnimatorListener(new AnimationSuccessListener() {
@Override
@@ -213,27 +240,7 @@
if (mPipContentOverlay != null) {
mPipContentOverlay.onAnimationUpdate(tx, mCurrentBounds, progress);
}
- final PictureInPictureSurfaceTransaction op;
- if (mSourceHintRectInsets == null) {
- // no source rect hint been set, directly scale the window down
- op = onAnimationScale(progress, tx, mCurrentBounds);
- } else {
- // scale and crop according to the source rect hint
- op = onAnimationScaleAndCrop(progress, tx, mCurrentBounds);
- }
- return op;
- }
-
- /** scale the window directly with no source rect hint being set */
- private PictureInPictureSurfaceTransaction onAnimationScale(
- float progress, SurfaceControl.Transaction tx, Rect bounds) {
- if (mFromRotation == Surface.ROTATION_90 || mFromRotation == Surface.ROTATION_270) {
- final RotatedPosition rotatedPosition = getRotatedPosition(progress);
- return mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds,
- rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
- } else {
- return mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds);
- }
+ return onAnimationScaleAndCrop(progress, tx, mCurrentBounds);
}
/** scale and crop the window with source rect hint */
@@ -268,6 +275,10 @@
return mAppBounds;
}
+ public Rect getSourceRectHint() {
+ return mSourceRectHint;
+ }
+
@Nullable
public SurfaceControl getContentOverlay() {
return mPipContentOverlay == null ? null : mPipContentOverlay.getLeash();
diff --git a/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt b/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt
new file mode 100644
index 0000000..5f4388c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util
+
+import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
+
+/** Util class for holding and checking [SystemUiStateFlags] masks. */
+object SystemUiFlagUtils {
+ const val KEYGUARD_SYSUI_FLAGS =
+ (QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING or
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING or
+ QuickStepContract.SYSUI_STATE_DEVICE_DOZING or
+ QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED or
+ QuickStepContract.SYSUI_STATE_HOME_DISABLED or
+ QuickStepContract.SYSUI_STATE_BACK_DISABLED or
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED or
+ QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK)
+
+ // If any of these SysUi flags (via QuickstepContract) is set, the device to be considered
+ // locked.
+ private const val MASK_ANY_SYSUI_LOCKED =
+ (QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING or
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING or
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED or
+ QuickStepContract.SYSUI_STATE_DEVICE_DREAMING)
+
+ /**
+ * Returns true iff the given [SystemUiStateFlags] imply that the device is considered locked.
+ */
+ @JvmStatic
+ fun isLocked(@SystemUiStateFlags flags: Long): Boolean {
+ return hasAnyFlag(flags, MASK_ANY_SYSUI_LOCKED) &&
+ !hasAnyFlag(flags, QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY)
+ }
+
+ private fun hasAnyFlag(@SystemUiStateFlags flags: Long, flagMask: Long): Boolean {
+ return (flags and flagMask) != 0L
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index 9268511..304b8f4 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -26,9 +26,11 @@
import android.view.WindowMetrics;
import com.android.internal.policy.SystemBarUtils;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.CachedDisplayInfo;
import com.android.launcher3.util.window.WindowManagerProxy;
+import com.android.quickstep.LauncherActivityInterface;
import java.util.List;
import java.util.Set;
@@ -49,6 +51,13 @@
}
@Override
+ public boolean isInDesktopMode() {
+ DesktopVisibilityController desktopController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ return desktopController != null && desktopController.areDesktopTasksVisible();
+ }
+
+ @Override
public int getRotation(Context displayInfoContext) {
return displayInfoContext.getResources().getConfiguration().windowConfiguration
.getRotation();
diff --git a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
index 3756b4a..d74473c 100644
--- a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
@@ -27,6 +27,8 @@
public int getPlaceholderIconFadeInEnd() { return 250; }
public int getStagedRectSlideStart() { return 0; }
public int getStagedRectSlideEnd() { return 500; }
+ public int getBackingScrimFadeInStart() { return 0; }
+ public int getBackingScrimFadeInEnd() { return 400; }
public int getDuration() { return TABLET_CONFIRM_DURATION; }
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
index 21c9e09..69137cc 100644
--- a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
+++ b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
@@ -37,7 +37,7 @@
* @param <V> Type of object stored in the cache
*/
public class TaskKeyByLastActiveTimeCache<V> implements TaskKeyCache<V> {
- private static final String TAG = TaskKeyByLastActiveTimeCache.class.getSimpleName();
+ private static final String TAG = "TaskKeyByLastActiveTimeCache";
private final AtomicInteger mMaxSize;
private final Map<Integer, Entry<V>> mMap;
// To sort task id by last active time
diff --git a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
index cdadd71..e80d2a6 100644
--- a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
+++ b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
@@ -22,10 +22,12 @@
import static com.android.launcher3.BaseActivity.EVENT_RESUMED;
import static com.android.launcher3.BaseActivity.EVENT_STOPPED;
+import android.content.Context;
+
import androidx.annotation.NonNull;
-import com.android.launcher3.BaseActivity;
import com.android.quickstep.RecentsModel;
+import com.android.quickstep.views.RecentsViewContainer;
/**
* This class tracks the failure of a task launch through the TaskView.launchTask() call, in an
@@ -38,27 +40,33 @@
*/
public class TaskRemovedDuringLaunchListener {
- private BaseActivity mActivity;
+ private RecentsViewContainer mContainer;
private int mLaunchedTaskId = INVALID_TASK_ID;
private Runnable mTaskLaunchFailedCallback = null;
private final Runnable mUnregisterCallback = this::unregister;
private final Runnable mResumeCallback = this::checkTaskLaunchFailed;
+ private final Context mContext;
+
+ public TaskRemovedDuringLaunchListener(Context context) {
+ mContext = context;
+ }
+
/**
* Registers a failure listener callback if it detects a scenario in which an app launch
* failed before the transition finished.
*/
- public void register(BaseActivity activity, int launchedTaskId,
+ public void register(RecentsViewContainer container, int launchedTaskId,
@NonNull Runnable taskLaunchFailedCallback) {
// The normal task launch case, Launcher stops and updates its state correctly
- activity.addEventCallback(EVENT_STOPPED, mUnregisterCallback);
+ container.addEventCallback(EVENT_STOPPED, mUnregisterCallback);
// The transition hasn't finished but Launcher was resumed, check if the launch failed
- activity.addEventCallback(EVENT_RESUMED, mResumeCallback);
+ container.addEventCallback(EVENT_RESUMED, mResumeCallback);
// If we somehow don't get any of the above signals, then just unregister this listener
- activity.addEventCallback(EVENT_DESTROYED, mUnregisterCallback);
+ container.addEventCallback(EVENT_DESTROYED, mUnregisterCallback);
- mActivity = activity;
+ mContainer = container;
mLaunchedTaskId = launchedTaskId;
mTaskLaunchFailedCallback = taskLaunchFailedCallback;
}
@@ -67,11 +75,11 @@
* Unregisters the failure listener.
*/
private void unregister() {
- mActivity.removeEventCallback(EVENT_STOPPED, mUnregisterCallback);
- mActivity.removeEventCallback(EVENT_RESUMED, mResumeCallback);
- mActivity.removeEventCallback(EVENT_DESTROYED, mUnregisterCallback);
+ mContainer.removeEventCallback(EVENT_STOPPED, mUnregisterCallback);
+ mContainer.removeEventCallback(EVENT_RESUMED, mResumeCallback);
+ mContainer.removeEventCallback(EVENT_DESTROYED, mUnregisterCallback);
- mActivity = null;
+ mContainer = null;
mLaunchedTaskId = INVALID_TASK_ID;
mTaskLaunchFailedCallback = null;
}
@@ -88,7 +96,7 @@
if (mLaunchedTaskId != INVALID_TASK_ID) {
final int launchedTaskId = mLaunchedTaskId;
final Runnable taskLaunchFailedCallback = mTaskLaunchFailedCallback;
- RecentsModel.INSTANCE.getNoCreate().isTaskRemoved(mLaunchedTaskId, (taskRemoved) -> {
+ RecentsModel.INSTANCE.get(mContext).isTaskRemoved(mLaunchedTaskId, (taskRemoved) -> {
if (taskRemoved) {
ActiveGestureLog.INSTANCE.addLog(
new ActiveGestureLog.CompoundString("Launch failed, task (id=")
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 1152de2..49f4e5f 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -51,6 +51,7 @@
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
import com.android.quickstep.views.TaskView.FullscreenDrawParams;
@@ -70,7 +71,7 @@
private final float[] mTempPoint = new float[2];
private final Context mContext;
- private final BaseActivityInterface mSizeStrategy;
+ private final BaseContainerInterface mSizeStrategy;
@NonNull
private RecentsOrientedState mOrientationState;
@@ -122,7 +123,7 @@
private int mTaskRectTranslationX;
private int mTaskRectTranslationY;
- public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
+ public TaskViewSimulator(Context context, BaseContainerInterface sizeStrategy) {
mContext = context;
mSizeStrategy = sizeStrategy;
@@ -244,7 +245,7 @@
if (mSplitBounds == null) {
mStagePosition = STAGE_POSITION_UNDEFINED;
} else {
- mStagePosition = mThumbnailPosition.equals(splitInfo.leftTopBounds)
+ mStagePosition = runningTarget.taskId == splitInfo.leftTopTaskId
? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
mPositionHelper.setSplitBounds(convertLauncherSplitBoundsToShell(mSplitBounds),
mStagePosition);
@@ -550,7 +551,7 @@
* TaskView
*/
public float getCurrentCornerRadius() {
- float visibleRadius = mCurrentFullscreenParams.mCurrentDrawnCornerRadius;
+ float visibleRadius = mCurrentFullscreenParams.getCurrentDrawnCornerRadius();
mTempPoint[0] = visibleRadius;
mTempPoint[1] = 0;
mInversePositionMatrix.mapVectors(mTempPoint);
diff --git a/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java b/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
index 7bbde30..671b2ea 100644
--- a/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
+++ b/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
@@ -28,6 +28,8 @@
import android.view.MotionEvent;
import android.view.VelocityTracker;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -41,21 +43,20 @@
private final float mMinFlingVelocity;
private final boolean mDisableHorizontalSwipe;
private final NavBarPosition mNavBarPosition;
- private final Runnable mOnInterceptTouch;
+
+ @NonNull
private final OnSwipeUpListener mOnSwipeUp;
private boolean mInterceptedTouch;
private VelocityTracker mVelocityTracker;
public TriggerSwipeUpTouchTracker(Context context, boolean disableHorizontalSwipe,
- NavBarPosition navBarPosition, Runnable onInterceptTouch,
- OnSwipeUpListener onSwipeUp) {
+ NavBarPosition navBarPosition, @NonNull OnSwipeUpListener onSwipeUp) {
mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
mMinFlingVelocity = context.getResources().getDimension(
R.dimen.quickstep_fling_threshold_speed);
mNavBarPosition = navBarPosition;
mDisableHorizontalSwipe = disableHorizontalSwipe;
- mOnInterceptTouch = onInterceptTouch;
mOnSwipeUp = onSwipeUp;
init();
@@ -98,21 +99,20 @@
if (mDisableHorizontalSwipe
&& Math.abs(displacementX) > Math.abs(displacementY)) {
// Horizontal gesture is not allowed in this region
+ mOnSwipeUp.onSwipeUpCancelled();
endTouchTracking();
break;
}
mInterceptedTouch = true;
-
- if (mOnInterceptTouch != null) {
- mOnInterceptTouch.run();
- }
+ mOnSwipeUp.onSwipeUpTouchIntercepted();
}
}
break;
}
case ACTION_CANCEL:
+ mOnSwipeUp.onSwipeUpCancelled();
endTouchTracking();
break;
@@ -124,7 +124,8 @@
}
}
- private void endTouchTracking() {
+ /** Finishes the tracking. All events after this call are ignored */
+ public void endTouchTracking() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
@@ -151,12 +152,10 @@
isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
}
- if (mOnSwipeUp != null) {
- if (isSwipeUp) {
- mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY));
- } else {
- mOnSwipeUp.onSwipeUpCancelled();
- }
+ if (isSwipeUp) {
+ mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY));
+ } else {
+ mOnSwipeUp.onSwipeUpCancelled();
}
}
@@ -172,6 +171,9 @@
void onSwipeUp(boolean wasFling, PointF finalVelocity);
/** Called on touch up if a swipe up was not detected. */
- void onSwipeUpCancelled();
+ default void onSwipeUpCancelled() { }
+
+ /** Called when the touch for swipe up is intercepted. */
+ default void onSwipeUpTouchIntercepted() { }
}
}
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
index c8141b4..a740e0b 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
@@ -20,7 +20,8 @@
import android.view.WindowManager;
import com.android.launcher3.Hotseat;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.systemui.unfold.dagger.UnfoldMain;
import com.android.systemui.unfold.updates.RotationChangeProvider;
/**
@@ -28,10 +29,11 @@
*/
public class UnfoldMoveFromCenterHotseatAnimator extends BaseUnfoldMoveFromCenterAnimator {
- private final Launcher mLauncher;
+ private final QuickstepLauncher mLauncher;
- public UnfoldMoveFromCenterHotseatAnimator(Launcher launcher, WindowManager windowManager,
- RotationChangeProvider rotationChangeProvider) {
+ public UnfoldMoveFromCenterHotseatAnimator(
+ QuickstepLauncher launcher, WindowManager windowManager,
+ @UnfoldMain RotationChangeProvider rotationChangeProvider) {
super(windowManager, rotationChangeProvider);
mLauncher = launcher;
}
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
index c05b38f..6e330bb 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -19,9 +19,10 @@
import android.view.WindowManager;
import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Workspace;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.systemui.unfold.dagger.UnfoldMain;
import com.android.systemui.unfold.updates.RotationChangeProvider;
/**
@@ -29,10 +30,11 @@
*/
public class UnfoldMoveFromCenterWorkspaceAnimator extends BaseUnfoldMoveFromCenterAnimator {
- private final Launcher mLauncher;
+ private final QuickstepLauncher mLauncher;
- public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager,
- RotationChangeProvider rotationChangeProvider) {
+ public UnfoldMoveFromCenterWorkspaceAnimator(
+ QuickstepLauncher launcher, WindowManager windowManager,
+ @UnfoldMain RotationChangeProvider rotationChangeProvider) {
super(windowManager, rotationChangeProvider);
mLauncher = launcher;
}
diff --git a/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt b/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt
index 54d317d..09563f5 100644
--- a/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt
+++ b/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt
@@ -21,15 +21,15 @@
import com.android.launcher3.Alarm
import com.android.launcher3.DeviceProfile
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
-import com.android.launcher3.Launcher
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.uioverrides.QuickstepLauncher
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
/** Controls animations that are happening during unfolding foldable devices */
class LauncherUnfoldTransitionController(
- private val launcher: Launcher,
+ private val launcher: QuickstepLauncher,
private val progressProvider: ProxyUnfoldTransitionProvider
) : OnDeviceProfileChangeListener, ActivityLifecycleCallbacksAdapter, TransitionProgressListener {
diff --git a/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt b/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt
index d2c4728..2f90ee7 100644
--- a/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt
+++ b/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt
@@ -20,7 +20,6 @@
import com.android.app.animation.Interpolators.LINEAR
import com.android.app.animation.Interpolators.clampToProgress
import com.android.launcher3.CellLayout
-import com.android.launcher3.Launcher
import com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY
import com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_UNFOLD_ANIMATION
import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
@@ -28,6 +27,7 @@
import com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY
import com.android.launcher3.Workspace
import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.uioverrides.QuickstepLauncher
import com.android.launcher3.util.HorizontalInsettableView
private typealias ViewGroupAction = (ViewGroup, Boolean) -> Unit
@@ -112,7 +112,7 @@
* Builds an animation for the unfold experience and adds it to the provided PendingAnimation
*/
fun buildUnfoldAnimation(
- launcher: Launcher,
+ launcher: QuickstepLauncher,
isVerticalFold: Boolean,
screenSize: Point,
anim: PendingAnimation
diff --git a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
index fdc8f1f..121d8ed 100644
--- a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_EDU_SHOWN;
+import static com.android.quickstep.util.AnimUtils.clampToDuration;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -187,10 +188,14 @@
intro.setInterpolator(LINEAR);
intro.setDuration(introDuration);
intro.addUpdateListener((new MultiValueUpdateListener() {
- FloatProp mCircleAlpha = new FloatProp(0, 255, 0, firstPart, LINEAR);
- FloatProp mCircleScale = new FloatProp(2f, 1f, 0, firstPart, OVERSHOOT_1_7);
- FloatProp mDeltaY = new FloatProp(0, transY, firstPart, secondPart, FAST_OUT_SLOW_IN);
- FloatProp mGradientAlpha = new FloatProp(0, 255, firstPart, secondPart * 0.3f, LINEAR);
+ FloatProp mCircleAlpha = new FloatProp(0, 255,
+ clampToDuration(LINEAR, 0, firstPart, introDuration));
+ FloatProp mCircleScale = new FloatProp(2f, 1f,
+ clampToDuration(OVERSHOOT_1_7, 0, firstPart, introDuration));
+ FloatProp mDeltaY = new FloatProp(0, transY,
+ clampToDuration(FAST_OUT_SLOW_IN, firstPart, secondPart, introDuration));
+ FloatProp mGradientAlpha = new FloatProp(0, 255,
+ clampToDuration(LINEAR, firstPart, secondPart * 0.3f, introDuration));
@Override
public void onUpdate(float progress, boolean initOnly) {
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index 32ef904..c3efc3c 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -17,15 +17,27 @@
package com.android.quickstep.views;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.widget.Button;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.Flags;
+import com.android.launcher3.R;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.util.BorderAnimator;
+
+import kotlin.Unit;
public class ClearAllButton extends Button {
@@ -55,7 +67,7 @@
}
};
- private final StatefulActivity mActivity;
+ private final RecentsViewContainer mContainer;
private float mScrollAlpha = 1;
private float mContentAlpha = 1;
private float mVisibilityAlpha = 1;
@@ -71,11 +83,75 @@
private float mScrollOffsetPrimary;
private int mSidePadding;
+ private int mOutlinePadding;
+ private boolean mBorderEnabled;
+ @Nullable
+ private final BorderAnimator mFocusBorderAnimator;
public ClearAllButton(Context context, AttributeSet attrs) {
super(context, attrs);
mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- mActivity = StatefulActivity.fromContext(context);
+ mContainer = RecentsViewContainer.containerFromContext(context);
+
+ if (Flags.enableFocusOutline()) {
+ TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
+ R.styleable.ClearAllButton);
+ Resources resources = getResources();
+ mOutlinePadding = resources.getDimensionPixelSize(
+ R.dimen.recents_clear_all_outline_padding);
+ mFocusBorderAnimator =
+ BorderAnimator.createSimpleBorderAnimator(
+ /* borderRadiusPx= */ resources.getDimensionPixelSize(
+ R.dimen.recents_clear_all_outline_radius),
+ /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_border_width),
+ /* boundsBuilder= */ this::updateBorderBounds,
+ /* targetView= */ this,
+ /* borderColor= */ styledAttrs.getColor(
+ R.styleable.ClearAllButton_focusBorderColor,
+ DEFAULT_BORDER_COLOR));
+ styledAttrs.recycle();
+ } else {
+ mFocusBorderAnimator = null;
+ }
+ }
+
+ private Unit updateBorderBounds(@NonNull Rect bounds) {
+ bounds.set(0, 0, getWidth(), getHeight());
+ // Make the value negative to form a padding between button and outline
+ bounds.inset(-mOutlinePadding, -mOutlinePadding);
+ return Unit.INSTANCE;
+ }
+
+ @Override
+ public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ if (mFocusBorderAnimator != null && mBorderEnabled) {
+ mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
+ }
+ }
+
+ /**
+ * Enable or disable showing border on focus change
+ */
+ public void setBorderEnabled(boolean enabled) {
+ if (mBorderEnabled == enabled) {
+ return;
+ }
+
+ mBorderEnabled = enabled;
+ if (mFocusBorderAnimator != null) {
+ mFocusBorderAnimator.setBorderVisibility(/* visible= */
+ enabled && isFocused(), /* animated= */true);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mFocusBorderAnimator != null) {
+ mFocusBorderAnimator.drawBorder(canvas);
+ }
+ super.draw(canvas);
}
@Override
@@ -253,7 +329,7 @@
* Get the Y translation that is set in the original layout position, before scrolling.
*/
private float getOriginalTranslationY() {
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ DeviceProfile deviceProfile = mContainer.getDeviceProfile();
if (deviceProfile.isTablet) {
if (enableGridOnlyOverview()) {
return (getRecentsView().getLastComputedTaskSize().height()
diff --git a/quickstep/src/com/android/quickstep/views/DesktopAppSelectView.java b/quickstep/src/com/android/quickstep/views/DesktopAppSelectView.java
deleted file mode 100644
index a5be142..0000000
--- a/quickstep/src/com/android/quickstep/views/DesktopAppSelectView.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.views;
-
-import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.app.animation.Interpolators.LINEAR;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-
-/**
- * Floating view show on launcher home screen that notifies the user that an app will be launched to
- * the desktop.
- */
-public class DesktopAppSelectView extends LinearLayout {
-
- private static final int SHOW_INITIAL_HEIGHT_DP = 7;
- private static final int SHOW_CONTAINER_SCALE_DURATION = 333;
- private static final int SHOW_CONTAINER_ALPHA_DURATION = 83;
- private static final int SHOW_CONTENT_ALPHA_DELAY = 67;
- private static final int SHOW_CONTENT_ALPHA_DURATION = 83;
- private static final int HIDE_DURATION = 83;
-
- private final Launcher mLauncher;
-
- private View mText;
- private View mCloseButton;
- @Nullable
- private Runnable mOnCloseCallback;
- private AnimatorSet mShowAnimation;
- private Animator mHideAnimation;
-
- public DesktopAppSelectView(Context context) {
- this(context, null);
- }
-
- public DesktopAppSelectView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public DesktopAppSelectView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public DesktopAppSelectView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mLauncher = Launcher.getLauncher(context);
- }
-
- /**
- * Show the popup on launcher home screen
- *
- * @param onCloseCallback optional callback that is called when user clicks the close button
- * @return the created view
- */
- public static DesktopAppSelectView show(Launcher launcher, @Nullable Runnable onCloseCallback) {
- DesktopAppSelectView view = (DesktopAppSelectView) launcher.getLayoutInflater().inflate(
- R.layout.floating_desktop_app_select, launcher.getDragLayer(), false);
- view.setOnCloseClickCallback(onCloseCallback);
- view.show();
- return view;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mText = findViewById(R.id.desktop_app_select_text);
- mCloseButton = findViewById(R.id.close_button);
- mCloseButton.setOnClickListener(v -> {
- if (mHideAnimation == null) {
- hide();
- if (mOnCloseCallback != null) {
- mOnCloseCallback.run();
- }
- }
- });
- }
-
- private void show() {
- mLauncher.getDragLayer().addView(this);
-
- // Set up initial values
- getBackground().setAlpha(0);
- mText.setAlpha(0);
- mCloseButton.setAlpha(0);
- int initialHeightPx = Utilities.dpToPx(SHOW_INITIAL_HEIGHT_DP);
- int finalHeight = getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_floating_app_select_height);
- float initialScale = initialHeightPx / (float) finalHeight;
- setScaleY(initialScale);
- setPivotY(0);
-
- // Animate the container
- ValueAnimator containerBackground = ValueAnimator.ofInt(0, 255);
- containerBackground.addUpdateListener(
- animation -> getBackground().setAlpha((Integer) animation.getAnimatedValue()));
- containerBackground.setDuration(SHOW_CONTAINER_ALPHA_DURATION);
- containerBackground.setInterpolator(LINEAR);
-
- ObjectAnimator containerSize = ObjectAnimator.ofFloat(this, SCALE_Y, 1f);
- containerSize.setDuration(SHOW_CONTAINER_SCALE_DURATION);
- containerSize.setInterpolator(EMPHASIZED_DECELERATE);
-
- // Animate the contents
- ObjectAnimator textAlpha = ObjectAnimator.ofFloat(mText, ALPHA, 1);
- ObjectAnimator buttonAlpha = ObjectAnimator.ofFloat(mCloseButton, ALPHA, 1);
- AnimatorSet contentAlpha = new AnimatorSet();
- contentAlpha.playTogether(textAlpha, buttonAlpha);
- contentAlpha.setStartDelay(SHOW_CONTENT_ALPHA_DELAY);
- contentAlpha.setDuration(SHOW_CONTENT_ALPHA_DURATION);
- contentAlpha.setInterpolator(LINEAR);
-
- // Start the animation
- mShowAnimation = new AnimatorSet();
- mShowAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mShowAnimation = null;
- }
- });
- mShowAnimation.playTogether(containerBackground, containerSize, contentAlpha);
- mShowAnimation.start();
- }
-
- /**
- * Hide the floating view
- */
- public void hide() {
- if (mShowAnimation != null) {
- mShowAnimation.cancel();
- }
- mHideAnimation = ObjectAnimator.ofFloat(this, ALPHA, 0);
- mHideAnimation.setDuration(HIDE_DURATION).setInterpolator(LINEAR);
- mHideAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mLauncher.getDragLayer().removeView(DesktopAppSelectView.this);
- mHideAnimation = null;
- }
- });
- mHideAnimation.start();
- }
-
- /**
- * Add a callback that is called when close button is clicked
- */
- public void setOnCloseClickCallback(@Nullable Runnable callback) {
- mOnCloseCallback = callback;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
deleted file mode 100644
index f2c9f27..0000000
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RoundRectShape;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.desktop.DesktopRecentsTransitionController;
-import com.android.launcher3.icons.IconProvider;
-import com.android.launcher3.util.CancellableTask;
-import com.android.launcher3.util.RunnableList;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.util.RecentsOrientedState;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.window.flags.Flags;
-
-import kotlin.Unit;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * TaskView that contains all tasks that are part of the desktop.
- */
-// TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
-public class DesktopTaskView extends TaskView {
-
- private static final String TAG = DesktopTaskView.class.getSimpleName();
-
- private static final boolean DEBUG = false;
-
- @NonNull
- private List<Task> mTasks = new ArrayList<>();
-
- private final ArrayList<TaskThumbnailView> mSnapshotViews = new ArrayList<>();
-
- /** Maps {@code taskIds} to corresponding {@link TaskThumbnailView}s */
- private final SparseArray<TaskThumbnailView> mSnapshotViewMap = new SparseArray<>();
-
- private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
-
- private final TaskView.FullscreenDrawParams mSnapshotDrawParams;
-
- private View mBackgroundView;
-
- private int mChildCountAtInflation;
-
- /** Check whether desktop windowing is enabled */
- public static boolean isDesktopModeSupported() {
- return Flags.enableDesktopWindowingMode();
- }
-
- public DesktopTaskView(Context context) {
- this(context, null);
- }
-
- public DesktopTaskView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public DesktopTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- mSnapshotDrawParams = new FullscreenDrawParams(context) {
- @Override
- public float computeTaskCornerRadius(Context context) {
- return QuickStepContract.getWindowCornerRadius(context);
- }
-
- @Override
- public float computeWindowCornerRadius(Context context) {
- return QuickStepContract.getWindowCornerRadius(context);
- }
- };
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mBackgroundView = findViewById(R.id.background);
-
- int topMarginPx =
- mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
- FrameLayout.LayoutParams params = (LayoutParams) mBackgroundView.getLayoutParams();
- params.topMargin = topMarginPx;
- mBackgroundView.setLayoutParams(params);
-
- float[] outerRadii = new float[8];
- Arrays.fill(outerRadii, getTaskCornerRadius());
- RoundRectShape shape = new RoundRectShape(outerRadii, null, null);
- ShapeDrawable background = new ShapeDrawable(shape);
- background.setTint(getResources().getColor(android.R.color.system_neutral2_300,
- getContext().getTheme()));
- // TODO(b/244348395): this should be wallpaper
- mBackgroundView.setBackground(background);
-
- Drawable icon = getResources().getDrawable(R.drawable.ic_desktop, getContext().getTheme());
- Drawable iconBackground = getResources().getDrawable(R.drawable.bg_circle,
- getContext().getTheme());
- mIconView.setDrawable(new LayerDrawable(new Drawable[]{iconBackground, icon}));
-
- mChildCountAtInflation = getChildCount();
- }
-
- @Override
- protected Unit updateBorderBounds(@NonNull Rect bounds) {
- bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(), mBackgroundView.getRight(),
- mBackgroundView.getBottom());
- return Unit.INSTANCE;
- }
-
- @Override
- public void bind(Task task, RecentsOrientedState orientedState) {
- bind(Collections.singletonList(task), orientedState);
- }
-
- /**
- * Updates this desktop task to the gives task list defined in {@code tasks}
- */
- public void bind(List<Task> tasks, RecentsOrientedState orientedState) {
- if (DEBUG) {
- StringBuilder sb = new StringBuilder();
- sb.append("bind tasks=").append(tasks.size()).append("\n");
- for (Task task : tasks) {
- sb.append(" key=").append(task.key).append("\n");
- }
- Log.d(TAG, sb.toString());
- }
- cancelPendingLoadTasks();
-
- mTasks = new ArrayList<>(tasks);
- mSnapshotViewMap.clear();
-
- // Ensure there are equal number of snapshot views and tasks.
- // More tasks than views, add views. More views than tasks, remove views.
- // TODO(b/251586230): use a ViewPool for creating TaskThumbnailViews
- if (mSnapshotViews.size() > mTasks.size()) {
- int diff = mSnapshotViews.size() - mTasks.size();
- for (int i = 0; i < diff; i++) {
- TaskThumbnailView snapshotView = mSnapshotViews.remove(0);
- removeView(snapshotView);
- }
- } else if (mSnapshotViews.size() < mTasks.size()) {
- int diff = mTasks.size() - mSnapshotViews.size();
- for (int i = 0; i < diff; i++) {
- TaskThumbnailView snapshotView = new TaskThumbnailView(getContext());
- mSnapshotViews.add(snapshotView);
- // Add snapshots from to position after the initial child views.
- addView(snapshotView, mChildCountAtInflation,
- new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- }
- }
-
- for (int i = 0; i < mTasks.size(); i++) {
- Task task = mTasks.get(i);
- TaskThumbnailView snapshotView = mSnapshotViews.get(i);
- snapshotView.bind(task);
- mSnapshotViewMap.put(task.key.id, snapshotView);
- }
-
- updateTaskIdContainer();
- updateTaskIdAttributeContainer();
-
- setOrientationState(orientedState);
- }
-
- private void updateTaskIdContainer() {
- // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
- // At least 2 elements in the array
- mTaskIdContainer = new int[Math.max(mTasks.size(), 2)];
- for (int i = 0; i < mTasks.size(); i++) {
- mTaskIdContainer[i] = mTasks.get(i).key.id;
- }
- }
-
- private void updateTaskIdAttributeContainer() {
- // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
- // At least 2 elements in the array
- mTaskIdAttributeContainer = new TaskIdAttributeContainer[Math.max(mTasks.size(), 2)];
- for (int i = 0; i < mTasks.size(); i++) {
- Task task = mTasks.get(i);
- TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
- mTaskIdAttributeContainer[i] = createAttributeContainer(task, thumbnailView);
- }
- }
-
- private TaskIdAttributeContainer createAttributeContainer(Task task,
- TaskThumbnailView thumbnailView) {
- return new TaskIdAttributeContainer(task, thumbnailView, createIconView(task),
- STAGE_POSITION_UNDEFINED);
- }
-
- private IconView createIconView(Task task) {
- IconView iconView = new IconView(mContext);
- PackageManager pm = mContext.getApplicationContext().getPackageManager();
- try {
- IconProvider provider = new IconProvider(mContext);
- Drawable appIcon = provider.getIcon(pm.getActivityInfo(task.topActivity,
- PackageManager.ComponentInfoFlags.of(0)));
- iconView.setDrawable(appIcon);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Package not found: " + task.topActivity.getPackageName(), e);
- }
- return iconView;
- }
-
- @Nullable
- @Override
- public Task getTask() {
- // TODO(b/249371338): returning first task. This won't work well with multiple tasks.
- return mTasks.size() > 0 ? mTasks.get(0) : null;
- }
-
- @Override
- public TaskThumbnailView getThumbnail() {
- // TODO(b/249371338): returning single thumbnail. This won't work well with multiple tasks.
- Task task = getTask();
- if (task != null) {
- return mSnapshotViewMap.get(task.key.id);
- }
- // Return the place holder snapshot views. Callers expect this to be non-null
- return mSnapshotView;
- }
-
- @Override
- public boolean containsTaskId(int taskId) {
- // Thumbnail map contains taskId -> thumbnail map. Use the keys for contains
- return mSnapshotViewMap.contains(taskId);
- }
-
- @Override
- public void onTaskListVisibilityChanged(boolean visible, int changes) {
- cancelPendingLoadTasks();
- if (visible) {
- RecentsModel model = RecentsModel.INSTANCE.get(getContext());
- TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
-
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- for (Task task : mTasks) {
- CancellableTask<?> thumbLoadRequest =
- thumbnailCache.updateThumbnailInBackground(task, thumbnailData -> {
- TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
- if (thumbnailView != null) {
- thumbnailView.setThumbnail(task, thumbnailData);
- }
- });
- if (thumbLoadRequest != null) {
- mPendingThumbnailRequests.add(thumbLoadRequest);
- }
- }
- }
- } else {
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- for (Task task : mTasks) {
- TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
- if (thumbnailView != null) {
- thumbnailView.setThumbnail(null, null);
- }
- // Reset the task thumbnail ref
- task.thumbnail = null;
- }
- }
- }
- }
-
- @Override
- protected void setThumbnailOrientation(RecentsOrientedState orientationState) {
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
-
- LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
- snapshotParams.topMargin = thumbnailTopMargin;
-
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
- thumbnailView.setLayoutParams(snapshotParams);
- }
- }
-
- @Override
- protected void cancelPendingLoadTasks() {
- for (CancellableTask<?> cancellableTask : mPendingThumbnailRequests) {
- cancellableTask.cancel();
- }
- mPendingThumbnailRequests.clear();
- }
-
- @Override
- public boolean offerTouchToChildren(MotionEvent event) {
- return false;
- }
-
- @Override
- protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) {
- return false;
- }
-
- @Nullable
- @Override
- public RunnableList launchTaskAnimated() {
- RunnableList endCallback = new RunnableList();
-
- RecentsView recentsView = getRecentsView();
- DesktopRecentsTransitionController recentsController =
- recentsView.getDesktopRecentsController();
- if (recentsController != null) {
- recentsController.launchDesktopFromRecents(this, success -> {
- endCallback.executeAllAndDestroy();
- });
- }
-
- // Callbacks get run from recentsView for case when recents animation already running
- recentsView.addSideTaskLaunchCallback(endCallback);
- return endCallback;
- }
-
- @Override
- public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
- launchTasks();
- callback.accept(true);
- }
-
- @Override
- public boolean isDesktopTask() {
- return true;
- }
-
- @Override
- void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
- // Sets new thumbnails based on the incoming data and refreshes the rest.
- // Create a copy of the thumbnail map, so we can track thumbnails that need refreshing.
- SparseArray<TaskThumbnailView> thumbnailsToRefresh = mSnapshotViewMap.clone();
- if (thumbnailDatas != null) {
- for (Task task : mTasks) {
- int key = task.key.id;
- TaskThumbnailView thumbnailView = thumbnailsToRefresh.get(key);
- ThumbnailData thumbnailData = thumbnailDatas.get(key);
- if (thumbnailView != null && thumbnailData != null) {
- thumbnailView.setThumbnail(task, thumbnailData);
- // Remove this thumbnail from the list that should be refreshed.
- thumbnailsToRefresh.remove(key);
- }
- }
- }
-
- // Refresh the rest that were not updated.
- for (int i = 0; i < thumbnailsToRefresh.size(); i++) {
- thumbnailsToRefresh.valueAt(i).refresh();
- }
- }
-
- @Override
- public TaskThumbnailView[] getThumbnails() {
- TaskThumbnailView[] thumbnails = new TaskThumbnailView[mSnapshotViewMap.size()];
- for (int i = 0; i < thumbnails.length; i++) {
- thumbnails[i] = mSnapshotViewMap.valueAt(i);
- }
- return thumbnails;
- }
-
- @Override
- public void onRecycle() {
- resetPersistentViewTransforms();
- // Clear any references to the thumbnail (it will be re-read either from the cache or the
- // system on next bind)
- for (Task task : mTasks) {
- TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
- if (thumbnailView != null) {
- thumbnailView.setThumbnail(task, null);
- }
- }
- setOverlayEnabled(false);
- onTaskListVisibilityChanged(false);
- setVisibility(VISIBLE);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int containerWidth = MeasureSpec.getSize(widthMeasureSpec);
- int containerHeight = MeasureSpec.getSize(heightMeasureSpec);
-
- setMeasuredDimension(containerWidth, containerHeight);
-
- int thumbnailTopMarginPx = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
- containerHeight -= thumbnailTopMarginPx;
-
- int thumbnails = mSnapshotViewMap.size();
- if (thumbnails == 0) {
- return;
- }
-
- int windowWidth = mActivity.getDeviceProfile().widthPx;
- int windowHeight = mActivity.getDeviceProfile().heightPx;
-
- float scaleWidth = containerWidth / (float) windowWidth;
- float scaleHeight = containerHeight / (float) windowHeight;
-
- 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.
- for (int i = 0; i < mTasks.size(); i++) {
- Task task = mTasks.get(i);
- Rect taskSize = task.appBounds;
- if (taskSize == null) {
- // Default to quarter of the desktop if we did not get app bounds.
- taskSize = new Rect(0, 0, windowWidth / 4, windowHeight / 4);
- }
-
- int thumbWidth = (int) (taskSize.width() * scaleWidth);
- int thumbHeight = (int) (taskSize.height() * scaleHeight);
-
- TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
- if (thumbnailView != null) {
- thumbnailView.measure(MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY));
-
- // Position the task to the same position as it would be on the desktop
- Point positionInParent = task.positionInParent;
- if (positionInParent == null) {
- positionInParent = new Point(0, 0);
- }
- int taskX = (int) (positionInParent.x * scaleWidth);
- int taskY = (int) (positionInParent.y * scaleHeight);
- // move task down by margin size
- taskY += thumbnailTopMarginPx;
- thumbnailView.setX(taskX);
- thumbnailView.setY(taskY);
-
- if (DEBUG) {
- Log.d(TAG, "onMeasure: task=" + task.key + " thumb=[" + thumbWidth + ","
- + thumbHeight + "]" + " pos=[" + taskX + "," + taskY + "]");
- }
- }
- }
- }
-
- @Override
- public void setOverlayEnabled(boolean overlayEnabled) {
- // Intentional no-op to prevent setting smart actions overlay on thumbnails
- }
-
- @Override
- public void setFullscreenProgress(float progress) {
- // TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
- progress = Utilities.boundToRange(progress, 0, 1);
- mFullscreenProgress = progress;
- if (mFullscreenProgress > 0) {
- // Don't show background while we are transitioning to/from fullscreen
- mBackgroundView.setVisibility(INVISIBLE);
- } else {
- mBackgroundView.setVisibility(VISIBLE);
- }
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
- thumbnailView.getTaskOverlay().setFullscreenProgress(progress);
- }
- updateSnapshotRadius();
- }
-
- @Override
- protected void updateSnapshotRadius() {
- super.updateSnapshotRadius();
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- if (i == 0) {
- // All snapshots share the same params. Only update it with the first snapshot.
- updateFullscreenParams(mSnapshotDrawParams);
- }
- mSnapshotViewMap.valueAt(i).setFullscreenParams(mSnapshotDrawParams);
- }
- }
-
- @Override
- protected void setIconsAndBannersTransitionProgress(float progress, boolean invert) {
- // no-op
- }
-
- @Override
- public void setColorTint(float amount, int tintColor) {
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- mSnapshotViewMap.valueAt(i).setDimAlpha(amount);
- }
- }
-
- @Override
- protected void applyThumbnailSplashAlpha() {
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- mSnapshotViewMap.valueAt(i).setSplashAlpha(mTaskThumbnailSplashAlpha);
- }
- }
-
- @Override
- void setThumbnailVisibility(int visibility, int taskId) {
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- mSnapshotViewMap.valueAt(i).setVisibility(visibility);
- }
- }
-
- @Override
- protected boolean confirmSecondSplitSelectApp() {
- // Desktop tile can't be in split screen
- return false;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
new file mode 100644
index 0000000..936f6a1
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views
+
+import android.content.Context
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.drawable.LayerDrawable
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
+import android.util.AttributeSet
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.R
+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.set
+import com.android.quickstep.BaseContainerInterface
+import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.util.RecentsOrientedState
+import com.android.systemui.shared.recents.model.Task
+
+/** TaskView that contains all tasks that are part of the desktop. */
+class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+ TaskView(context, attrs) {
+
+ private val snapshotDrawParams =
+ object : FullscreenDrawParams(context) {
+ // DesktopTaskView thumbnail's corner radius is independent of fullscreenProgress.
+ override fun computeTaskCornerRadius(context: Context) =
+ computeWindowCornerRadius(context)
+ }
+ private val taskThumbnailViewPool =
+ ViewPool<TaskThumbnailViewDeprecated>(
+ context,
+ this,
+ R.layout.task_thumbnail,
+ VIEW_POOL_MAX_SIZE,
+ VIEW_POOL_INITIAL_SIZE
+ )
+ private val tempPointF = PointF()
+ private val tempRect = Rect()
+ private lateinit var backgroundView: View
+ private lateinit var iconView: TaskViewIcon
+ private var childCountAtInflation = 0
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ backgroundView =
+ findViewById<View>(R.id.background)!!.apply {
+ updateLayoutParams<LayoutParams> {
+ topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+ }
+ background =
+ ShapeDrawable(RoundRectShape(FloatArray(8) { taskCornerRadius }, null, null))
+ .apply {
+ setTint(
+ resources.getColor(
+ android.R.color.system_neutral2_300,
+ context.theme
+ )
+ )
+ }
+ }
+ iconView =
+ getOrInflateIconView(R.id.icon).apply {
+ val iconBackground = resources.getDrawable(R.drawable.bg_circle, context.theme)
+ val icon = resources.getDrawable(R.drawable.ic_desktop, context.theme)
+ setIcon(this, LayerDrawable(arrayOf(iconBackground, icon)))
+ }
+ childCountAtInflation = childCount
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val containerWidth = MeasureSpec.getSize(widthMeasureSpec)
+ var containerHeight = MeasureSpec.getSize(heightMeasureSpec)
+ setMeasuredDimension(containerWidth, containerHeight)
+
+ if (taskContainers.isEmpty()) {
+ return
+ }
+
+ val thumbnailTopMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+ containerHeight -= 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()
+ 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 thumbWidth = (taskSize.width() * scaleWidth).toInt()
+ val thumbHeight = (taskSize.height() * scaleHeight).toInt()
+ it.thumbnailViewDeprecated.measure(
+ MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY)
+ )
+
+ // Position the task to the same position as it would be on the desktop
+ val positionInParent = it.task.positionInParent ?: ORIGIN
+ val taskX = (positionInParent.x * scaleWidth).toInt()
+ var taskY = (positionInParent.y * scaleHeight).toInt()
+ // move task down by margin size
+ taskY += thumbnailTopMarginPx
+ it.thumbnailViewDeprecated.x = taskX.toFloat()
+ it.thumbnailViewDeprecated.y = taskY.toFloat()
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onMeasure: task=${it.task.key} thumb=[$thumbWidth,$thumbHeight]" +
+ " pos=[$taskX,$taskY]"
+ )
+ }
+ }
+ }
+
+ override fun onRecycle() {
+ super.onRecycle()
+ visibility = VISIBLE
+ }
+
+ /** Updates this desktop task to the gives task list defined in `tasks` */
+ fun bind(
+ tasks: List<Task>,
+ orientedState: RecentsOrientedState,
+ taskOverlayFactory: TaskOverlayFactory
+ ) {
+ if (DEBUG) {
+ val sb = StringBuilder()
+ sb.append("bind tasks=").append(tasks.size).append("\n")
+ tasks.forEach { sb.append(" key=${it.key}\n") }
+ Log.d(TAG, sb.toString())
+ }
+ cancelPendingLoadTasks()
+
+ if (!isTaskContainersInitialized()) {
+ taskContainers = arrayListOf()
+ }
+ val taskContainers = taskContainers as ArrayList
+ taskContainers.ensureCapacity(tasks.size)
+ tasks.forEachIndexed { index, task ->
+ val thumbnailViewDeprecated: TaskThumbnailViewDeprecated
+ if (index >= taskContainers.size) {
+ thumbnailViewDeprecated = taskThumbnailViewPool.view
+ // Add thumbnailView from to position after the initial child views.
+ addView(
+ thumbnailViewDeprecated,
+ childCountAtInflation,
+ LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ )
+ } else {
+ thumbnailViewDeprecated = taskContainers[index].thumbnailViewDeprecated
+ }
+ val taskContainer =
+ TaskContainer(
+ task,
+ // TODO(b/338360089): Support new TTV for DesktopTaskView
+ thumbnailView = null,
+ thumbnailViewDeprecated,
+ iconView,
+ TransformingTouchDelegate(iconView.asView()),
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+ digitalWellBeingToast = null,
+ showWindowsView = null,
+ taskOverlayFactory
+ )
+ .apply { thumbnailViewDeprecated.bind(task, overlay) }
+ if (index >= taskContainers.size) {
+ taskContainers.add(taskContainer)
+ } else {
+ taskContainers[index] = taskContainer
+ }
+ }
+ repeat(taskContainers.size - tasks.size) {
+ with(taskContainers.removeLast()) {
+ removeView(thumbnailViewDeprecated)
+ taskThumbnailViewPool.recycle(thumbnailViewDeprecated)
+ }
+ }
+
+ setOrientationState(orientedState)
+ }
+
+ override fun needsUpdate(dataChange: Int, flag: Int) =
+ if (flag == FLAG_UPDATE_THUMBNAIL) super.needsUpdate(dataChange, flag) else false
+
+ // thumbnailView is laid out differently and is handled in onMeasure
+ override fun updateThumbnailSize() {}
+
+ override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
+ if (relativeToDragLayer) {
+ container.dragLayer.getDescendantRectRelativeToSelf(backgroundView, bounds)
+ } else {
+ bounds.set(backgroundView)
+ }
+ }
+
+ override fun launchTaskAnimated(): RunnableList? {
+ val recentsView = recentsView ?: return null
+ val endCallback = RunnableList()
+ val desktopController = recentsView.desktopRecentsController
+ checkNotNull(desktopController) { "recentsController is null" }
+ desktopController.launchDesktopFromRecents(this) { endCallback.executeAllAndDestroy() }
+ Log.d(TAG, "launchTaskAnimated - launchDesktopFromRecents: ${taskIds.contentToString()}")
+
+ // Callbacks get run from recentsView for case when recents animation already running
+ recentsView.addSideTaskLaunchCallback(endCallback)
+ return endCallback
+ }
+
+ override fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) {
+ launchTasks()
+ callback(true)
+ }
+
+ // Desktop tile can't be in split screen
+ override fun confirmSecondSplitSelectApp(): Boolean = false
+
+ // TODO(b/330685808) support overlay for Screenshot action
+ override fun setOverlayEnabled(overlayEnabled: Boolean) {}
+
+ override fun onFullscreenProgressChanged(fullscreenProgress: Float) {
+ // Don't show background while we are transitioning to/from fullscreen
+ backgroundView.visibility = if (fullscreenProgress > 0) INVISIBLE else VISIBLE
+ }
+
+ override fun updateCurrentFullscreenParams() {
+ super.updateCurrentFullscreenParams()
+ updateFullscreenParams(snapshotDrawParams)
+ }
+
+ override fun getThumbnailFullscreenParams() = snapshotDrawParams
+
+ companion object {
+ private const val TAG = "DesktopTaskView"
+ private const val DEBUG = false
+ private const val VIEW_POOL_MAX_SIZE = 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)
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 840382d..4a5b9e4 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -19,10 +19,11 @@
import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS;
import static com.android.launcher3.Utilities.prefixTextWithIcon;
-import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
+import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.AppUsageLimit;
@@ -38,6 +39,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -45,12 +47,11 @@
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
+import com.android.quickstep.TaskUtils;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.systemui.shared.recents.model.Task;
@@ -70,27 +71,30 @@
private static final int SPLIT_GRID_BANNER_LARGE = 1;
/** Used for grid task view, only showing icon */
private static final int SPLIT_GRID_BANNER_SMALL = 2;
+
@IntDef(value = {
SPLIT_BANNER_FULLSCREEN,
SPLIT_GRID_BANNER_LARGE,
SPLIT_GRID_BANNER_SMALL,
})
@Retention(RetentionPolicy.SOURCE)
- @interface SPLIT_BANNER_CONFIG{}
+ @interface SplitBannerConfig {
+ }
static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
static final int MINUTE_MS = 60000;
- private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
+ private static final String TAG = "DigitalWellBeingToast";
- private final BaseDraggingActivity mActivity;
+ private final RecentsViewContainer mContainer;
private final TaskView mTaskView;
private final LauncherApps mLauncherApps;
+ private final int mBannerHeight;
+
private Task mTask;
private boolean mHasLimit;
- private long mAppUsageLimitTimeMs;
private long mAppRemainingTimeMs;
@Nullable
private View mBanner;
@@ -98,31 +102,33 @@
private float mBannerOffsetPercentage;
@Nullable
private SplitBounds mSplitBounds;
- private int mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
private float mSplitOffsetTranslationY;
private float mSplitOffsetTranslationX;
- public DigitalWellBeingToast(BaseDraggingActivity activity, TaskView taskView) {
- mActivity = activity;
+ private boolean mIsDestroyed = false;
+
+ public DigitalWellBeingToast(RecentsViewContainer container, TaskView taskView) {
+ mContainer = container;
mTaskView = taskView;
- mLauncherApps = activity.getSystemService(LauncherApps.class);
+ mLauncherApps = container.asContext().getSystemService(LauncherApps.class);
+ mBannerHeight = container.asContext().getResources().getDimensionPixelSize(
+ R.dimen.digital_wellbeing_toast_height);
}
private void setNoLimit() {
mHasLimit = false;
mTaskView.setContentDescription(mTask.titleDescription);
replaceBanner(null);
- mAppUsageLimitTimeMs = -1;
mAppRemainingTimeMs = -1;
}
private void setLimit(long appUsageLimitTimeMs, long appRemainingTimeMs) {
- mAppUsageLimitTimeMs = appUsageLimitTimeMs;
mAppRemainingTimeMs = appRemainingTimeMs;
mHasLimit = true;
- TextView toast = mActivity.getViewCache().getView(R.layout.digital_wellbeing_toast,
- mActivity, mTaskView);
- toast.setText(prefixTextWithIcon(mActivity, R.drawable.ic_hourglass_top, getText()));
+ TextView toast = mContainer.getViewCache().getView(R.layout.digital_wellbeing_toast,
+ mContainer.asContext(), mTaskView);
+ toast.setText(prefixTextWithIcon(mContainer.asContext(), R.drawable.ic_hourglass_top,
+ getText()));
toast.setOnClickListener(this::openAppUsageSettings);
replaceBanner(toast);
@@ -139,104 +145,109 @@
}
public void initialize(Task task) {
- mAppUsageLimitTimeMs = mAppRemainingTimeMs = -1;
+ if (mIsDestroyed) {
+ throw new IllegalStateException("Cannot re-initialize a destroyed toast");
+ }
mTask = task;
- THREAD_POOL_EXECUTOR.execute(() -> {
- AppUsageLimit usageLimit = null;
- try {
- usageLimit = mLauncherApps.getAppUsageLimit(
- mTask.getTopComponent().getPackageName(),
- UserHandle.of(mTask.key.userId));
- } catch (Exception e) {
- Log.e(TAG, "Error initializing digital well being toast", e);
- }
- final long appUsageLimitTimeMs =
- usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
- final long appRemainingTimeMs =
- usageLimit != null ? usageLimit.getUsageRemaining() : -1;
+ ORDERED_BG_EXECUTOR.execute(() -> {
+ AppUsageLimit usageLimit = null;
+ try {
+ usageLimit = mLauncherApps.getAppUsageLimit(
+ mTask.getTopComponent().getPackageName(),
+ UserHandle.of(mTask.key.userId));
+ } catch (Exception e) {
+ Log.e(TAG, "Error initializing digital well being toast", e);
+ }
+ final long appUsageLimitTimeMs =
+ usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
+ final long appRemainingTimeMs =
+ usageLimit != null ? usageLimit.getUsageRemaining() : -1;
- mTaskView.post(() -> {
- if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
- setNoLimit();
- } else {
- setLimit(appUsageLimitTimeMs, appRemainingTimeMs);
- }
- });
-
+ mTaskView.post(() -> {
+ if (mIsDestroyed) {
+ return;
}
- );
+ if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
+ setNoLimit();
+ } else {
+ setLimit(appUsageLimitTimeMs, appRemainingTimeMs);
+ }
+ });
+ });
}
- public void setSplitConfiguration(SplitBounds splitBounds) {
+ /**
+ * Mark the DWB toast as destroyed and remove banner from TaskView.
+ */
+ public void destroy() {
+ mIsDestroyed = true;
+ mTaskView.post(() -> replaceBanner(null));
+ }
+
+ public void setSplitBounds(@Nullable SplitBounds splitBounds) {
mSplitBounds = splitBounds;
+ }
+
+ private @SplitBannerConfig int getSplitBannerConfig() {
if (mSplitBounds == null
- || !mActivity.getDeviceProfile().isTablet
+ || !mContainer.getDeviceProfile().isTablet
|| mTaskView.isFocusedTask()) {
- mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
- return;
+ return SPLIT_BANNER_FULLSCREEN;
}
// For portrait grid only height of task changes, not width. So we keep the text the same
- if (!mActivity.getDeviceProfile().isLeftRightSplit) {
- mSplitBannerConfig = SPLIT_GRID_BANNER_LARGE;
- return;
+ if (!mContainer.getDeviceProfile().isLeftRightSplit) {
+ return SPLIT_GRID_BANNER_LARGE;
}
// For landscape grid, for 30% width we only show icon, otherwise show icon and time
if (mTask.key.id == mSplitBounds.leftTopTaskId) {
- mSplitBannerConfig = mSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY ?
- SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
+ return mSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY
+ ? SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
} else {
- mSplitBannerConfig = mSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY ?
- SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
+ return mSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY
+ ? SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
}
}
private String getReadableDuration(
Duration duration,
- FormatWidth formatWidthHourAndMinute,
- @StringRes int durationLessThanOneMinuteStringId,
- boolean forceFormatWidth) {
+ @StringRes int durationLessThanOneMinuteStringId) {
int hours = Math.toIntExact(duration.toHours());
int minutes = Math.toIntExact(duration.minusHours(hours).toMinutes());
- // Apply formatWidthHourAndMinute if both the hour part and the minute part are non-zero.
+ // Apply FormatWidth.WIDE if both the hour part and the minute part are non-zero.
if (hours > 0 && minutes > 0) {
- return MeasureFormat.getInstance(Locale.getDefault(), formatWidthHourAndMinute)
+ return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.NARROW)
.formatMeasures(
new Measure(hours, MeasureUnit.HOUR),
new Measure(minutes, MeasureUnit.MINUTE));
}
- // Apply formatWidthHourOrMinute if only the hour part is non-zero (unless forced).
+ // Apply FormatWidth.WIDE if only the hour part is non-zero (unless forced).
if (hours > 0) {
- return MeasureFormat.getInstance(
- Locale.getDefault(),
- forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
- .formatMeasures(new Measure(hours, MeasureUnit.HOUR));
+ return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures(
+ new Measure(hours, MeasureUnit.HOUR));
}
- // Apply formatWidthHourOrMinute if only the minute part is non-zero (unless forced).
+ // Apply FormatWidth.WIDE if only the minute part is non-zero (unless forced).
if (minutes > 0) {
- return MeasureFormat.getInstance(
- Locale.getDefault()
- , forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
- .formatMeasures(new Measure(minutes, MeasureUnit.MINUTE));
+ return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures(
+ new Measure(minutes, MeasureUnit.MINUTE));
}
// Use a specific string for usage less than one minute but non-zero.
if (duration.compareTo(Duration.ZERO) > 0) {
- return mActivity.getString(durationLessThanOneMinuteStringId);
+ return mContainer.asContext().getString(durationLessThanOneMinuteStringId);
}
// Otherwise, return 0-minute string.
- return MeasureFormat.getInstance(
- Locale.getDefault(), forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
- .formatMeasures(new Measure(0, MeasureUnit.MINUTE));
+ return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures(
+ new Measure(0, MeasureUnit.MINUTE));
}
/**
- * Returns text to show for the banner depending on {@link #mSplitBannerConfig}
+ * Returns text to show for the banner depending on {@link #getSplitBannerConfig()}
* If {@param forContentDesc} is {@code true}, this will always return the full
* string corresponding to {@link #SPLIT_BANNER_FULLSCREEN}
*/
@@ -246,16 +257,16 @@
(remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS :
remainingTime);
String readableDuration = getReadableDuration(duration,
- FormatWidth.NARROW,
- R.string.shorter_duration_less_than_one_minute,
- false /* forceFormatWidth */);
- if (forContentDesc || mSplitBannerConfig == SPLIT_BANNER_FULLSCREEN) {
- return mActivity.getString(
+ R.string.shorter_duration_less_than_one_minute
+ /* forceFormatWidth */);
+ @SplitBannerConfig int splitBannerConfig = getSplitBannerConfig();
+ if (forContentDesc || splitBannerConfig == SPLIT_BANNER_FULLSCREEN) {
+ return mContainer.asContext().getString(
R.string.time_left_for_app,
readableDuration);
}
- if (mSplitBannerConfig == SPLIT_GRID_BANNER_SMALL) {
+ if (splitBannerConfig == SPLIT_GRID_BANNER_SMALL) {
// show no text
return "";
} else { // SPLIT_GRID_BANNER_LARGE
@@ -270,11 +281,12 @@
mTask.getTopComponent().getPackageName()).addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
try {
- final BaseActivity activity = BaseActivity.fromContext(view.getContext());
+ final RecentsViewContainer container =
+ RecentsViewContainer.containerFromContext(view.getContext());
final ActivityOptions options = ActivityOptions.makeScaleUpAnimation(
view, 0, 0,
view.getWidth(), view.getHeight());
- activity.startActivity(intent, options.toBundle());
+ container.asContext().startActivity(intent, options.toBundle());
// TODO: add WW logging on the app usage settings click.
} catch (ActivityNotFoundException e) {
@@ -286,7 +298,7 @@
private String getContentDescriptionForTask(
Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) {
return appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0 ?
- mActivity.getString(
+ mContainer.asContext().getString(
R.string.task_contents_description_with_remaining_time,
task.titleDescription,
getText(appRemainingTimeMs, true /* forContentDesc */)) :
@@ -303,13 +315,13 @@
mBanner.setOutlineProvider(mOldBannerOutlineProvider);
mTaskView.removeView(mBanner);
mBanner.setOnClickListener(null);
- mActivity.getViewCache().recycleView(R.layout.digital_wellbeing_toast, mBanner);
+ mContainer.getViewCache().recycleView(R.layout.digital_wellbeing_toast, mBanner);
}
}
private void setBanner(@Nullable View view) {
mBanner = view;
- if (view != null && mTaskView.getRecentsView() != null) {
+ if (mBanner != null && mTaskView.getRecentsView() != null) {
setupAndAddBanner();
setBannerOutline();
}
@@ -318,14 +330,14 @@
private void setupAndAddBanner() {
FrameLayout.LayoutParams layoutParams =
(FrameLayout.LayoutParams) mBanner.getLayoutParams();
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ DeviceProfile deviceProfile = mContainer.getDeviceProfile();
layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams)
- mTaskView.getThumbnail().getLayoutParams()).bottomMargin;
+ mTaskView.getFirstThumbnailViewDeprecated().getLayoutParams()).bottomMargin;
RecentsPagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
Pair<Float, Float> translations = orientationHandler
.getDwbLayoutTranslations(mTaskView.getMeasuredWidth(),
mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile,
- mTaskView.getThumbnails(), mTask.key.id, mBanner);
+ mTaskView.getThumbnailViews(), mTask.key.id, mBanner);
mSplitOffsetTranslationX = translations.first;
mSplitOffsetTranslationY = translations.second;
updateTranslationY();
@@ -351,10 +363,12 @@
}
void updateBannerOffset(float offsetPercentage) {
- if (mBanner != null && mBannerOffsetPercentage != offsetPercentage) {
+ if (mBannerOffsetPercentage != offsetPercentage) {
mBannerOffsetPercentage = offsetPercentage;
- updateTranslationY();
- mBanner.invalidateOutline();
+ if (mBanner != null) {
+ updateTranslationY();
+ mBanner.invalidateOutline();
+ }
}
}
@@ -364,7 +378,7 @@
}
mBanner.setTranslationY(
- (mBannerOffsetPercentage * mBanner.getHeight()) + mSplitOffsetTranslationY);
+ (mBannerOffsetPercentage * mBannerHeight) + mSplitOffsetTranslationY);
}
private void updateTranslationX() {
@@ -395,4 +409,36 @@
mBanner.setVisibility(visibility);
}
+
+ private int getAccessibilityActionId() {
+ return (mSplitBounds != null
+ && mSplitBounds.rightBottomTaskId == mTask.key.id)
+ ? R.id.action_digital_wellbeing_bottom_right
+ : R.id.action_digital_wellbeing_top_left;
+ }
+
+ @Nullable
+ public AccessibilityNodeInfo.AccessibilityAction getDWBAccessibilityAction() {
+ if (!hasLimit()) {
+ return null;
+ }
+
+ Context context = mContainer.asContext();
+ String label =
+ (mTaskView.containsMultipleTasks())
+ ? context.getString(
+ R.string.split_app_usage_settings,
+ TaskUtils.getTitle(context, mTask)
+ ) : context.getString(R.string.accessibility_app_usage_settings);
+ return new AccessibilityNodeInfo.AccessibilityAction(getAccessibilityActionId(), label);
+ }
+
+ public boolean handleAccessibilityAction(int action) {
+ if (getAccessibilityActionId() == action) {
+ openAppUsageSettings(mTaskView);
+ return true;
+ } else {
+ return false;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
index 1c1e167..e024995 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
+++ b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
@@ -26,7 +26,6 @@
import android.os.Build
import android.view.animation.Interpolator
import com.android.app.animation.Interpolators
-import com.android.launcher3.Launcher
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.quickstep.util.AnimUtils
@@ -37,17 +36,18 @@
* animation. Consists of a rectangular background that splits into two, and two app icons that
* increase in size during the animation.
*/
-class FloatingAppPairBackground(
- context: Context,
- private val floatingView: FloatingAppPairView, // the view that we will draw this background on
- private val appIcon1: Drawable,
- private val appIcon2: Drawable,
- dividerPos: Int
+open class FloatingAppPairBackground(
+ context: Context,
+ // the view that we will draw this background on
+ protected val floatingView: FloatingAppPairView,
+ private val appIcon1: Drawable,
+ private val appIcon2: Drawable?,
+ dividerPos: Int
) : Drawable() {
companion object {
// Design specs -- app icons start small and expand during the animation
- private val STARTING_ICON_SIZE_PX = Utilities.dpToPx(22f)
- private val ENDING_ICON_SIZE_PX = Utilities.dpToPx(66f)
+ internal val STARTING_ICON_SIZE_PX = Utilities.dpToPx(22f)
+ internal val ENDING_ICON_SIZE_PX = Utilities.dpToPx(66f)
// Null values to use with drawDoubleRoundRect(), since there doesn't seem to be any other
// API for drawing rectangles with 4 different corner radii.
@@ -55,30 +55,30 @@
private val ARRAY_OF_ZEROES = FloatArray(8)
}
- private val launcher: Launcher
+ private val container: RecentsViewContainer
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
// Animation interpolators
- private val expandXInterpolator: Interpolator
- private val expandYInterpolator: Interpolator
+ protected val expandXInterpolator: Interpolator
+ protected val expandYInterpolator: Interpolator
private val cellSplitInterpolator: Interpolator
- private val iconFadeInterpolator: Interpolator
+ protected val iconFadeInterpolator: Interpolator
// Device-specific measurements
- private val deviceCornerRadius: Float
+ protected val deviceCornerRadius: Float
private val deviceHalfDividerSize: Float
private val desiredSplitRatio: Float
init {
- launcher = Launcher.getLauncher(context)
- val dp = launcher.deviceProfile
+ container = RecentsViewContainer.containerFromContext(context)
+ val dp = container.deviceProfile
// Set up background paint color
val ta = context.theme.obtainStyledAttributes(R.styleable.FolderIconPreview)
backgroundPaint.style = Paint.Style.FILL
backgroundPaint.color = ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0)
ta.recycle()
// Set up timings and interpolators
- val timings = AnimUtils.getDeviceAppPairLaunchTimings(launcher.deviceProfile.isTablet)
+ val timings = AnimUtils.getDeviceAppPairLaunchTimings(container.deviceProfile.isTablet)
expandXInterpolator =
Interpolators.clampToProgress(
timings.getStagedRectScaleXInterpolator(),
@@ -105,9 +105,9 @@
)
// Find device-specific measurements
- deviceCornerRadius = QuickStepContract.getWindowCornerRadius(launcher)
+ deviceCornerRadius = QuickStepContract.getWindowCornerRadius(container.asContext())
deviceHalfDividerSize =
- launcher.resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f
+ container.asContext().resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f
val dividerCenterPos = dividerPos + deviceHalfDividerSize
desiredSplitRatio =
if (dp.isLeftRightSplit) dividerCenterPos / dp.widthPx
@@ -115,7 +115,7 @@
}
override fun draw(canvas: Canvas) {
- if (launcher.deviceProfile.isLeftRightSplit) {
+ if (container.deviceProfile.isLeftRightSplit) {
drawLeftRightSplit(canvas)
} else {
drawTopBottomSplit(canvas)
@@ -150,39 +150,37 @@
val dividerCenterPos = width * desiredSplitRatio
// The left half of the background image
- val leftSide = RectF(
- 0f,
- 0f,
- dividerCenterPos - changingDividerSize,
- height
- )
+ val leftSide = RectF(0f, 0f, dividerCenterPos - changingDividerSize, height)
// The right half of the background image
- val rightSide = RectF(
- dividerCenterPos + changingDividerSize,
- 0f,
- width,
- height
- )
+ val rightSide = RectF(dividerCenterPos + changingDividerSize, 0f, width, height)
// Draw background
drawCustomRoundedRect(
canvas,
leftSide,
floatArrayOf(
- cornerRadiusX, cornerRadiusY,
- changingInnerRadiusX, changingInnerRadiusY,
- changingInnerRadiusX, changingInnerRadiusY,
- cornerRadiusX, cornerRadiusY
+ cornerRadiusX,
+ cornerRadiusY,
+ changingInnerRadiusX,
+ changingInnerRadiusY,
+ changingInnerRadiusX,
+ changingInnerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
)
)
drawCustomRoundedRect(
canvas,
rightSide,
floatArrayOf(
- changingInnerRadiusX, changingInnerRadiusY,
- cornerRadiusX, cornerRadiusY,
- cornerRadiusX, cornerRadiusY,
- changingInnerRadiusX, changingInnerRadiusY
+ changingInnerRadiusX,
+ changingInnerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
+ changingInnerRadiusX,
+ changingInnerRadiusY,
)
)
@@ -217,7 +215,7 @@
canvas.save()
canvas.translate(changingIcon2Left, changingIconTop)
canvas.scale(changingIconScaleX, changingIconScaleY)
- appIcon2.alpha = changingIconAlpha
+ appIcon2!!.alpha = changingIconAlpha
appIcon2.draw(canvas)
canvas.restore()
}
@@ -250,39 +248,37 @@
val dividerCenterPos = height * desiredSplitRatio
// The top half of the background image
- val topSide = RectF(
- 0f,
- 0f,
- width,
- dividerCenterPos - changingDividerSize
- )
+ val topSide = RectF(0f, 0f, width, dividerCenterPos - changingDividerSize)
// The bottom half of the background image
- val bottomSide = RectF(
- 0f,
- dividerCenterPos + changingDividerSize,
- width,
- height
- )
+ val bottomSide = RectF(0f, dividerCenterPos + changingDividerSize, width, height)
// Draw background
drawCustomRoundedRect(
canvas,
topSide,
floatArrayOf(
- cornerRadiusX, cornerRadiusY,
- cornerRadiusX, cornerRadiusY,
- changingInnerRadiusX, changingInnerRadiusY,
- changingInnerRadiusX, changingInnerRadiusY
+ cornerRadiusX,
+ cornerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
+ changingInnerRadiusX,
+ changingInnerRadiusY,
+ changingInnerRadiusX,
+ changingInnerRadiusY
)
)
drawCustomRoundedRect(
canvas,
bottomSide,
floatArrayOf(
- changingInnerRadiusX, changingInnerRadiusY,
- changingInnerRadiusX, changingInnerRadiusY,
- cornerRadiusX, cornerRadiusY,
- cornerRadiusX, cornerRadiusY
+ changingInnerRadiusX,
+ changingInnerRadiusY,
+ changingInnerRadiusX,
+ changingInnerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY
)
)
@@ -317,7 +313,7 @@
canvas.save()
canvas.translate(changingIconLeft, changingIcon2Top)
canvas.scale(changingIconScaleX, changingIconScaleY)
- appIcon2.alpha = changingIconAlpha
+ appIcon2!!.alpha = changingIconAlpha
appIcon2.draw(canvas)
canvas.restore()
}
@@ -330,7 +326,7 @@
* @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top
* right y, bottom right x, and so on.
*/
- private fun drawCustomRoundedRect(c: Canvas, rect: RectF, radii: FloatArray) {
+ protected fun drawCustomRoundedRect(c: Canvas, rect: RectF, radii: FloatArray) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Canvas.drawDoubleRoundRect is supported from Q onward
c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, backgroundPaint)
@@ -338,8 +334,10 @@
// Fallback rectangle with uniform rounded corners
val scaleFactorX = floatingView.scaleX
val scaleFactorY = floatingView.scaleY
- val cornerRadiusX = QuickStepContract.getWindowCornerRadius(launcher) / scaleFactorX
- val cornerRadiusY = QuickStepContract.getWindowCornerRadius(launcher) / scaleFactorY
+ val cornerRadiusX =
+ QuickStepContract.getWindowCornerRadius(container.asContext()) / scaleFactorX
+ val cornerRadiusY =
+ QuickStepContract.getWindowCornerRadius(container.asContext()) / scaleFactorY
c.drawRoundRect(rect, cornerRadiusX, cornerRadiusY, backgroundPaint)
}
}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingAppPairView.kt b/quickstep/src/com/android/quickstep/views/FloatingAppPairView.kt
index e90aa13..e8d1cc1 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingAppPairView.kt
+++ b/quickstep/src/com/android/quickstep/views/FloatingAppPairView.kt
@@ -40,8 +40,8 @@
fun getFloatingAppPairView(
launcher: StatefulActivity<*>,
originalView: View,
- appIcon1: Drawable,
- appIcon2: Drawable,
+ appIcon1: Drawable?,
+ appIcon2: Drawable?,
dividerPos: Int
): FloatingAppPairView {
val dragLayer: ViewGroup = launcher.getDragLayer()
@@ -64,8 +64,8 @@
fun init(
launcher: StatefulActivity<*>,
originalView: View,
- appIcon1: Drawable,
- appIcon2: Drawable,
+ appIcon1: Drawable?,
+ appIcon2: Drawable?,
dividerPos: Int
) {
val viewBounds = Rect(0, 0, originalView.width, originalView.height)
@@ -92,7 +92,14 @@
layoutParams = lp
// Prepare to draw app pair icon background
- background = FloatingAppPairBackground(context, this, appIcon1, appIcon2, dividerPos)
+ background = if (appIcon1 == null || appIcon2 == null) {
+ val iconToAnimate = appIcon1 ?: appIcon2
+ checkNotNull(iconToAnimate)
+ FloatingFullscreenAppPairBackground(context, this, iconToAnimate,
+ dividerPos)
+ } else {
+ FloatingAppPairBackground(context, this, appIcon1, appIcon2, dividerPos)
+ }
background.setBounds(0, 0, lp.width, lp.height)
}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingFullscreenAppPairBackground.kt b/quickstep/src/com/android/quickstep/views/FloatingFullscreenAppPairBackground.kt
new file mode 100644
index 0000000..8cd997f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/FloatingFullscreenAppPairBackground.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.views
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.RectF
+import android.graphics.drawable.Drawable
+
+class FloatingFullscreenAppPairBackground(
+ context: Context,
+ floatingView: FloatingAppPairView,
+ private val iconToLaunch: Drawable,
+ dividerPos: Int) :
+ FloatingAppPairBackground(
+ context,
+ floatingView,
+ iconToLaunch,
+ null /*appIcon2*/,
+ dividerPos
+) {
+
+ /** Animates the background as if launching a fullscreen task. */
+ override fun draw(canvas: Canvas) {
+ val progress = floatingView.progress
+
+ // Since the entire floating app pair surface is scaling up during this animation, we
+ // scale down most of these drawn elements so that they appear the proper size on-screen.
+ val scaleFactorX = floatingView.scaleX
+ val scaleFactorY = floatingView.scaleY
+
+ // Get the bounds where we will draw the background image
+ val width = bounds.width().toFloat()
+ val height = bounds.height().toFloat()
+
+ // Get device-specific measurements
+ val cornerRadiusX = deviceCornerRadius / scaleFactorX
+ val cornerRadiusY = deviceCornerRadius / scaleFactorY
+
+ // Draw background
+ drawCustomRoundedRect(
+ canvas,
+ RectF(0f, 0f, width, height),
+ floatArrayOf(
+ cornerRadiusX,
+ cornerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
+ )
+ )
+
+ // Calculate changing measurements for icon.
+ val changingIconSizeX =
+ (STARTING_ICON_SIZE_PX +
+ ((ENDING_ICON_SIZE_PX - STARTING_ICON_SIZE_PX) *
+ expandXInterpolator.getInterpolation(progress))) / scaleFactorX
+ val changingIconSizeY =
+ (STARTING_ICON_SIZE_PX +
+ ((ENDING_ICON_SIZE_PX - STARTING_ICON_SIZE_PX) *
+ expandYInterpolator.getInterpolation(progress))) / scaleFactorY
+
+ val changingIcon1Left = (width / 2f) - (changingIconSizeX / 2f)
+ val changingIconTop = (height / 2f) - (changingIconSizeY / 2f)
+ val changingIconScaleX = changingIconSizeX / iconToLaunch.bounds.width()
+ val changingIconScaleY = changingIconSizeY / iconToLaunch.bounds.height()
+ val changingIconAlpha =
+ (255 - (255 * iconFadeInterpolator.getInterpolation(progress))).toInt()
+
+ // Draw icon
+ canvas.save()
+ canvas.translate(changingIcon1Left, changingIconTop)
+ canvas.scale(changingIconScaleX, changingIconScaleY)
+ iconToLaunch.alpha = changingIconAlpha
+ iconToLaunch.draw(canvas)
+ canvas.restore()
+ }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java
index d869fed..e5a7333 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java
@@ -31,7 +31,7 @@
/**
* A child view of {@link com.android.quickstep.views.FloatingTaskView} to draw the thumbnail in a
* rounded corner frame. While the purpose of this class sounds similar to
- * {@link TaskThumbnailView}, it doesn't need a lot of complex logic in {@link TaskThumbnailView}
+ * {@link TaskThumbnailViewDeprecated}, it doesn't need a lot of complex logic in {@link TaskThumbnailViewDeprecated}
* in relation to moving with {@link RecentsView}.
*/
public class FloatingTaskThumbnailView extends View {
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index 12a073f..acbb2ec 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -36,12 +36,10 @@
import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.views.BaseDragLayer;
@@ -54,7 +52,7 @@
/**
* Create an instance via
- * {@link #getFloatingTaskView(StatefulActivity, View, Bitmap, Drawable, RectF)} to
+ * {@link #getFloatingTaskView(RecentsViewContainer, View, Bitmap, Drawable, RectF)} to
* which will have the thumbnail from the provided existing TaskView overlaying the taskview itself.
*
* Can then animate the taskview using
@@ -67,32 +65,32 @@
public static final FloatProperty<FloatingTaskView> PRIMARY_TRANSLATE_OFFSCREEN =
new FloatProperty<FloatingTaskView>("floatingTaskPrimaryTranslateOffscreen") {
- @Override
- public void setValue(FloatingTaskView view, float translation) {
- ((RecentsView) view.mActivity.getOverviewPanel()).getPagedOrientationHandler()
- .setFloatingTaskPrimaryTranslation(
- view,
- translation,
- view.mActivity.getDeviceProfile()
- );
- }
+ @Override
+ public void setValue(FloatingTaskView view, float translation) {
+ ((RecentsView) view.mContainer.getOverviewPanel()).getPagedOrientationHandler()
+ .setFloatingTaskPrimaryTranslation(
+ view,
+ translation,
+ view.mContainer.getDeviceProfile()
+ );
+ }
- @Override
- public Float get(FloatingTaskView view) {
- return ((RecentsView) view.mActivity.getOverviewPanel())
- .getPagedOrientationHandler()
- .getFloatingTaskPrimaryTranslation(
- view,
- view.mActivity.getDeviceProfile()
- );
- }
- };
+ @Override
+ public Float get(FloatingTaskView view) {
+ return ((RecentsView) view.mContainer.getOverviewPanel())
+ .getPagedOrientationHandler()
+ .getFloatingTaskPrimaryTranslation(
+ view,
+ view.mContainer.getDeviceProfile()
+ );
+ }
+ };
private int mSplitHolderSize;
private FloatingTaskThumbnailView mThumbnailView;
private SplitPlaceholderView mSplitPlaceholderView;
private RectF mStartingPosition;
- private final StatefulActivity mActivity;
+ private final RecentsViewContainer mContainer;
private final boolean mIsRtl;
private final FullscreenDrawParams mFullscreenParams;
private RecentsPagedOrientationHandler mOrientationHandler;
@@ -110,7 +108,7 @@
public FloatingTaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mActivity = BaseActivity.fromContext(context);
+ mContainer = RecentsViewContainer.containerFromContext(context);
mIsRtl = Utilities.isRtl(getResources());
mFullscreenParams = new FullscreenDrawParams(context);
@@ -126,7 +124,7 @@
mSplitPlaceholderView.setAlpha(0);
}
- private void init(StatefulActivity launcher, View originalView, @Nullable Bitmap thumbnail,
+ private void init(RecentsViewContainer launcher, View originalView, @Nullable Bitmap thumbnail,
Drawable icon, RectF positionOut) {
mStartingPosition = positionOut;
updateInitialPositionForView(originalView);
@@ -153,7 +151,7 @@
* Configures and returns a an instance of {@link FloatingTaskView} initially matching the
* appearance of {@code originalView}.
*/
- public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher,
+ public static FloatingTaskView getFloatingTaskView(RecentsViewContainer launcher,
View originalView, @Nullable Bitmap thumbnail, Drawable icon, RectF positionOut) {
final ViewGroup dragLayer = launcher.getDragLayer();
final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater()
@@ -167,7 +165,7 @@
// Add to top if not
openTaskViewIndex = dragLayer.getChildCount();
}
- dragLayer.addView(floatingView, openTaskViewIndex - 1);
+ dragLayer.addView(floatingView, openTaskViewIndex);
return floatingView;
}
@@ -179,17 +177,23 @@
originalView.getBoundsOnScreen(mTmpRect);
mStartingPosition.set(mTmpRect);
int[] dragLayerPositionRelativeToScreen =
- mActivity.getDragLayer().getLocationOnScreen();
+ mContainer.getDragLayer().getLocationOnScreen();
mStartingPosition.offset(
-dragLayerPositionRelativeToScreen[0],
-dragLayerPositionRelativeToScreen[1]);
} else {
Rect viewBounds = new Rect(0, 0, originalView.getWidth(), originalView.getHeight());
- Utilities.getBoundsForViewInDragLayer(mActivity.getDragLayer(), originalView,
+ Utilities.getBoundsForViewInDragLayer(mContainer.getDragLayer(), originalView,
viewBounds, false /* ignoreTransform */, null /* recycle */,
mStartingPosition);
}
-
+ // In some cases originalView is off-screen so we don't get a valid starting position
+ // ex. on rotation
+ // TODO(b/345556328) We shouldn't be animating if starting position of view isn't ready
+ if (mStartingPosition.isEmpty()) {
+ // Set to non empty for now so calculations in #update() don't break
+ mStartingPosition.set(0, 0, 1, 1);
+ }
final BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(
Math.round(mStartingPosition.width()),
Math.round(mStartingPosition.height()));
@@ -235,7 +239,7 @@
// Position the floating view exactly on top of the original
lp.topMargin = Math.round(pos.top);
if (mIsRtl) {
- lp.setMarginStart(mActivity.getDeviceProfile().widthPx - Math.round(pos.right));
+ lp.setMarginStart(mContainer.getDeviceProfile().widthPx - Math.round(pos.right));
} else {
lp.setMarginStart(Math.round(pos.left));
}
@@ -252,7 +256,7 @@
*/
public void addStagingAnimation(PendingAnimation animation, RectF startingBounds,
Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) {
- boolean isTablet = mActivity.getDeviceProfile().isTablet;
+ boolean isTablet = mContainer.getDeviceProfile().isTablet;
boolean splittingFromOverview = fadeWithThumbnail;
SplitAnimationTimings timings;
@@ -276,7 +280,7 @@
public void addConfirmAnimation(PendingAnimation animation, RectF startingBounds,
Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) {
SplitAnimationTimings timings =
- AnimUtils.getDeviceSplitToConfirmTimings(mActivity.getDeviceProfile().isTablet);
+ AnimUtils.getDeviceSplitToConfirmTimings(mContainer.getDeviceProfile().isTablet);
addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask,
timings);
@@ -291,7 +295,7 @@
Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask,
SplitAnimationTimings timings) {
mFullscreenParams.setIsStagedTask(isStagedTask);
- final BaseDragLayer dragLayer = mActivity.getDragLayer();
+ final BaseDragLayer dragLayer = mContainer.getDragLayer();
int[] dragLayerBounds = new int[2];
dragLayer.getLocationOnScreen(dragLayerBounds);
SplitOverlayProperties prop = new SplitOverlayProperties(endBounds,
@@ -299,7 +303,6 @@
ValueAnimator transitionAnimator = ValueAnimator.ofFloat(0, 1);
animation.add(transitionAnimator);
- long animDuration = animation.getDuration();
RectF floatingTaskViewBounds = new RectF();
if (fadeWithThumbnail) {
@@ -328,20 +331,20 @@
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
// SplitPlaceholderView: rectangle translates and stretches to new position
- final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration,
+ final FloatProp mDx = new FloatProp(0, prop.dX,
clampToProgress(timings.getStagedRectXInterpolator(),
timings.getStagedRectSlideStartOffset(),
timings.getStagedRectSlideEndOffset()));
- final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration,
+ final FloatProp mDy = new FloatProp(0, prop.dY,
clampToProgress(timings.getStagedRectYInterpolator(),
timings.getStagedRectSlideStartOffset(),
timings.getStagedRectSlideEndOffset()));
- final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0,
- animDuration, clampToProgress(timings.getStagedRectScaleXInterpolator(),
+ final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX,
+ clampToProgress(timings.getStagedRectScaleXInterpolator(),
timings.getStagedRectSlideStartOffset(),
timings.getStagedRectSlideEndOffset()));
- final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, 0,
- animDuration, clampToProgress(timings.getStagedRectScaleYInterpolator(),
+ final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY,
+ clampToProgress(timings.getStagedRectScaleYInterpolator(),
timings.getStagedRectSlideStartOffset(),
timings.getStagedRectSlideEndOffset()));
@Override
@@ -391,7 +394,7 @@
mOrientationHandler.updateSplitIconParams(iconView, onScreenRectCenterX,
onScreenRectCenterY, mFullscreenParams.mScaleX, mFullscreenParams.mScaleY,
iconView.getDrawableWidth(), iconView.getDrawableHeight(),
- mActivity.getDeviceProfile(), mStagePosition);
+ mContainer.getDeviceProfile(), mStagePosition);
}
public int getStagePosition() {
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
index 6431bdf..4dde635 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
@@ -33,10 +33,10 @@
import androidx.annotation.Nullable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.FloatingView;
import com.android.launcher3.views.ListenerView;
@@ -49,7 +49,7 @@
OnGlobalLayoutListener, FloatingView {
private static final Matrix sTmpMatrix = new Matrix();
- private final Launcher mLauncher;
+ private final QuickstepLauncher mLauncher;
private final ListenerView mListenerView;
private final FloatingWidgetBackgroundView mBackgroundView;
private final RectF mBackgroundOffset = new RectF();
@@ -80,7 +80,7 @@
public FloatingWidgetView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = QuickstepLauncher.getLauncher(context);
mListenerView = new ListenerView(context, attrs);
mBackgroundView = new FloatingWidgetBackgroundView(context, attrs, defStyleAttr);
addView(mBackgroundView);
@@ -283,7 +283,7 @@
* @param windowSize the size of the window when launched
* @param windowCornerRadius the corner radius of the window
*/
- public static FloatingWidgetView getFloatingWidgetView(Launcher launcher,
+ public static FloatingWidgetView getFloatingWidgetView(QuickstepLauncher launcher,
LauncherAppWidgetHostView originalView, RectF widgetBackgroundPosition,
Size windowSize, float windowCornerRadius, boolean appTargetsAreTranslucent,
int fallbackBackgroundColor) {
@@ -305,7 +305,8 @@
*/
public static int getDefaultBackgroundColor(
Context context, RemoteAnimationTarget target) {
- return (target != null && target.taskInfo.taskDescription != null)
+ return (target != null && target.taskInfo != null
+ && target.taskInfo.taskDescription != null)
? target.taskInfo.taskDescription.getBackgroundColor()
: Themes.getColorBackground(context);
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
deleted file mode 100644
index 10ef47c..0000000
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ /dev/null
@@ -1,520 +0,0 @@
-package com.android.quickstep.views;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.Pair;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.jank.Cuj;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.CancellableTask;
-import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.TransformingTouchDelegate;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.TaskIconCache;
-import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.util.RecentsOrientedState;
-import com.android.quickstep.util.SplitSelectStateController;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
-import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
-
-import kotlin.Unit;
-
-import java.util.HashMap;
-import java.util.function.Consumer;
-
-/**
- * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
- *
- * That's right. If you call within the next 5 minutes we'll go ahead and double your order and
- * send you !! TWO !! Tasks along with their TaskThumbnailViews complimentary. On. The. House.
- * And not only that, we'll even clean up your thumbnail request if you don't like it.
- * All the benefits of one TaskView, except DOUBLED!
- *
- * (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included).
- */
-public class GroupedTaskView extends TaskView {
-
- @Nullable
- private Task mSecondaryTask;
- private TaskThumbnailView mSnapshotView2;
- private TaskViewIcon mIconView2;
- @Nullable
- private CancellableTask<ThumbnailData> mThumbnailLoadRequest2;
- @Nullable
- private CancellableTask mIconLoadRequest2;
- private final float[] mIcon2CenterCoords = new float[2];
- private TransformingTouchDelegate mIcon2TouchDelegate;
- @Nullable private SplitBounds mSplitBoundsConfig;
- private final DigitalWellBeingToast mDigitalWellBeingToast2;
-
- public GroupedTaskView(Context context) {
- this(context, null);
- }
-
- public GroupedTaskView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public GroupedTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mDigitalWellBeingToast2 = new DigitalWellBeingToast(mActivity, this);
- }
-
- @Override
- protected Unit updateBorderBounds(@NonNull Rect bounds) {
- if (mSplitBoundsConfig == null) {
- super.updateBorderBounds(bounds);
- return Unit.INSTANCE;
- }
- bounds.set(
- Math.min(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()),
- mSnapshotView2.getLeft() + Math.round(mSnapshotView2.getTranslationX())),
- Math.min(mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()),
- mSnapshotView2.getTop() + Math.round(mSnapshotView2.getTranslationY())),
- Math.max(mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()),
- mSnapshotView2.getRight() + Math.round(mSnapshotView2.getTranslationX())),
- Math.max(mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()),
- mSnapshotView2.getBottom() + Math.round(mSnapshotView2.getTranslationY())));
- return Unit.INSTANCE;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSnapshotView2 = findViewById(R.id.bottomright_snapshot);
- ViewStub iconViewStub2 = findViewById(R.id.bottomRight_icon);
- if (enableOverviewIconMenu()) {
- iconViewStub2.setLayoutResource(R.layout.icon_app_chip_view);
- } else {
- iconViewStub2.setLayoutResource(R.layout.icon_view);
- }
- mIconView2 = (TaskViewIcon) iconViewStub2.inflate();
- mIcon2TouchDelegate = new TransformingTouchDelegate(mIconView2.asView());
- }
-
- public void bind(Task primary, Task secondary, RecentsOrientedState orientedState,
- @Nullable SplitBounds splitBoundsConfig) {
- super.bind(primary, orientedState);
- mSecondaryTask = secondary;
- mTaskIdContainer[1] = secondary.key.id;
- mTaskIdAttributeContainer[1] = new TaskIdAttributeContainer(secondary, mSnapshotView2,
- mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT);
- mTaskIdAttributeContainer[0].setStagePosition(
- SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT);
- mSnapshotView2.bind(secondary);
- mSplitBoundsConfig = splitBoundsConfig;
- if (mSplitBoundsConfig == null) {
- return;
- }
- mSnapshotView.getPreviewPositionHelper().setSplitBounds(
- convertLauncherSplitBoundsToShell(splitBoundsConfig),
- PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT);
- mSnapshotView2.getPreviewPositionHelper().setSplitBounds(
- convertLauncherSplitBoundsToShell(splitBoundsConfig),
- PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT);
- }
-
- /**
- * Sets up an on-click listener and the visibility for show_windows icon on top of each task.
- */
- @Override
- public void setUpShowAllInstancesListener() {
- // sets up the listener for the left/top task
- super.setUpShowAllInstancesListener();
-
- // right/bottom task's base package name
- String taskPackageName = mTaskIdAttributeContainer[1].getTask().key.getPackageName();
-
- // icon of the right/bottom task
- View showWindowsView = findViewById(R.id.show_windows_right);
- updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName));
- }
-
- @Override
- public void onTaskListVisibilityChanged(boolean visible, int changes) {
- super.onTaskListVisibilityChanged(visible, changes);
- if (visible) {
- RecentsModel model = RecentsModel.INSTANCE.get(getContext());
- TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
- TaskIconCache iconCache = model.getIconCache();
-
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- mThumbnailLoadRequest2 = thumbnailCache.updateThumbnailInBackground(mSecondaryTask,
- thumbnailData -> mSnapshotView2.setThumbnail(
- mSecondaryTask, thumbnailData
- ));
- }
-
- if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
- mIconLoadRequest2 = iconCache.updateIconInBackground(mSecondaryTask,
- (task) -> {
- setIcon(mIconView2, task.icon);
- if (enableOverviewIconMenu()) {
- setText(mIconView2, task.title);
- }
- mDigitalWellBeingToast2.initialize(mSecondaryTask);
- mDigitalWellBeingToast2.setSplitConfiguration(mSplitBoundsConfig);
- mDigitalWellBeingToast.setSplitConfiguration(mSplitBoundsConfig);
- });
- }
- } else {
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- mSnapshotView2.setThumbnail(null, null);
- // Reset the task thumbnail reference as well (it will be fetched from the cache or
- // reloaded next time we need it)
- mSecondaryTask.thumbnail = null;
- }
- if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
- setIcon(mIconView2, null);
- if (enableOverviewIconMenu()) {
- setText(mIconView2, null);
- }
- }
- }
- }
-
- public void updateSplitBoundsConfig(SplitBounds splitBounds) {
- mSplitBoundsConfig = splitBounds;
- invalidate();
- }
-
- @Nullable
- public SplitBounds getSplitBoundsConfig() {
- return mSplitBoundsConfig;
- }
-
- /**
- * Returns the {@link PersistentSnapPosition} of this pair of tasks.
- */
- public @PersistentSnapPosition int getSnapPosition() {
- if (mSplitBoundsConfig == null) {
- throw new IllegalStateException("mSplitBoundsConfig is null");
- }
-
- return mSplitBoundsConfig.snapPosition;
- }
-
- @Override
- public boolean offerTouchToChildren(MotionEvent event) {
- computeAndSetIconTouchDelegate(mIconView2, mIcon2CenterCoords, mIcon2TouchDelegate);
- if (mIcon2TouchDelegate.onTouchEvent(event)) {
- return true;
- }
-
- return super.offerTouchToChildren(event);
- }
-
- @Override
- protected void cancelPendingLoadTasks() {
- super.cancelPendingLoadTasks();
- if (mThumbnailLoadRequest2 != null) {
- mThumbnailLoadRequest2.cancel();
- mThumbnailLoadRequest2 = null;
- }
- if (mIconLoadRequest2 != null) {
- mIconLoadRequest2.cancel();
- mIconLoadRequest2 = null;
- }
- }
-
- @Nullable
- @Override
- public RunnableList launchTaskAnimated() {
- if (mTask == null || mSecondaryTask == null) {
- return null;
- }
-
- RunnableList endCallback = new RunnableList();
- RecentsView recentsView = getRecentsView();
- // Callbacks run from remote animation when recents animation not currently running
- InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER,
- "Enter form GroupedTaskView");
- launchTaskInternal(success -> {
- endCallback.executeAllAndDestroy();
- InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
- }, false /* freezeTaskList */, true /*launchingExistingTaskview*/);
-
-
- // Callbacks get run from recentsView for case when recents animation already running
- recentsView.addSideTaskLaunchCallback(endCallback);
- return endCallback;
- }
-
- @Override
- public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
- launchTaskInternal(callback, isQuickswitch, false /*launchingExistingTaskview*/);
- }
-
- /**
- * @param launchingExistingTaskView {@link SplitSelectStateController#launchExistingSplitPair}
- * uses existence of GroupedTaskView as control flow of how to animate in the incoming task. If
- * we're launching from overview (from overview thumbnails) then pass in {@code true},
- * otherwise pass in {@code false} for case like quickswitching from home to task
- */
- private void launchTaskInternal(@NonNull Consumer<Boolean> callback, boolean isQuickswitch,
- boolean launchingExistingTaskView) {
- getRecentsView().getSplitSelectController().launchExistingSplitPair(
- launchingExistingTaskView ? this : null, mTask.key.id,
- mSecondaryTask.key.id, SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
- callback, isQuickswitch, getSnapPosition());
- }
-
- @Override
- void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
- super.refreshThumbnails(thumbnailDatas);
- if (mSecondaryTask != null && thumbnailDatas != null) {
- final ThumbnailData thumbnailData = thumbnailDatas.get(mSecondaryTask.key.id);
- if (thumbnailData != null) {
- mSnapshotView2.setThumbnail(mSecondaryTask, thumbnailData);
- return;
- }
- }
-
- mSnapshotView2.refresh();
- }
-
- @Override
- public boolean containsTaskId(int taskId) {
- return (mTask != null && mTask.key.id == taskId)
- || (mSecondaryTask != null && mSecondaryTask.key.id == taskId);
- }
-
- @Override
- public TaskThumbnailView[] getThumbnails() {
- return new TaskThumbnailView[]{mSnapshotView, mSnapshotView2};
- }
-
- @Override
- protected int getLastSelectedChildTaskIndex() {
- SplitSelectStateController splitSelectController =
- getRecentsView().getSplitSelectController();
- if (splitSelectController.isDismissingFromSplitPair()) {
- // return the container index of the task that wasn't initially selected to split with
- // because that is the only remaining app that can be selected. The coordinate checks
- // below aren't reliable since both of those views may be gone/transformed
- int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
- if (initSplitTaskId != INVALID_TASK_ID) {
- return initSplitTaskId == mTask.key.id ? 1 : 0;
- }
- }
-
- // Check which of the two apps was selected
- if (isCoordInView(mIconView2.asView(), mLastTouchDownPosition)
- || isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
- return 1;
- }
- return super.getLastSelectedChildTaskIndex();
- }
-
- private boolean isCoordInView(View v, PointF position) {
- float[] localPos = new float[]{position.x, position.y};
- Utilities.mapCoordInSelfToDescendant(v, this, localPos);
- return Utilities.pointInView(v, localPos[0], localPos[1], 0f /* slop */);
- }
-
- @Override
- public void onRecycle() {
- super.onRecycle();
- mSnapshotView2.setThumbnail(mSecondaryTask, null);
- mSplitBoundsConfig = null;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- setMeasuredDimension(widthSize, heightSize);
- if (mSplitBoundsConfig == null || mSnapshotView == null || mSnapshotView2 == null) {
- return;
- }
- int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
- if (initSplitTaskId == INVALID_TASK_ID) {
- getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
- mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
- mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
- // Should we be having a separate translation step apart from the measuring above?
- // The following only applies to large screen for now, but for future reference
- // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
- // translation directions
- mSnapshotView.applySplitSelectTranslateX(mSnapshotView.getTranslationX());
- mSnapshotView.applySplitSelectTranslateY(mSnapshotView.getTranslationY());
- mSnapshotView2.applySplitSelectTranslateX(mSnapshotView2.getTranslationX());
- mSnapshotView2.applySplitSelectTranslateY(mSnapshotView2.getTranslationY());
- } else {
- // Currently being split with this taskView, let the non-split selected thumbnail
- // take up full thumbnail area
- TaskIdAttributeContainer container =
- mTaskIdAttributeContainer[initSplitTaskId == mTask.key.id ? 1 : 0];
- container.getThumbnailView().measure(widthMeasureSpec,
- View.MeasureSpec.makeMeasureSpec(
- heightSize -
- mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx,
- MeasureSpec.EXACTLY));
- }
- if (!enableOverviewIconMenu()) {
- updateIconPlacement();
- }
- }
-
- @Override
- public void setOverlayEnabled(boolean overlayEnabled) {
- // Intentional no-op to prevent setting smart actions overlay on thumbnails
- }
-
- @Override
- public void setOrientationState(RecentsOrientedState orientationState) {
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- if (enableOverviewIconMenu() && mSplitBoundsConfig != null) {
- ViewGroup.LayoutParams layoutParams = getLayoutParams();
- Pair<Point, Point> groupedTaskViewSizes =
- orientationState.getOrientationHandler().getGroupedTaskViewSizes(
- deviceProfile,
- mSplitBoundsConfig,
- layoutParams.width,
- layoutParams.height
- );
- int iconViewMarginStart = getResources().getDimensionPixelSize(
- R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin);
- int iconViewBackgroundMarginStart = getResources().getDimensionPixelSize(
- R.dimen.task_thumbnail_icon_menu_background_margin_top_start);
- int iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2;
- ((IconAppChipView) mIconView).setMaxWidth(groupedTaskViewSizes.first.x - iconMargins);
- ((IconAppChipView) mIconView2).setMaxWidth(groupedTaskViewSizes.second.x - iconMargins);
- }
- // setMaxWidth() needs to be called before mIconView.setIconOrientation which is called in
- // the super below.
- super.setOrientationState(orientationState);
-
- boolean isGridTask = deviceProfile.isTablet && !isFocusedTask();
- mIconView2.setIconOrientation(orientationState, isGridTask);
- updateIconPlacement();
- updateSecondaryDwbPlacement();
- }
-
- private void updateIconPlacement() {
- if (mSplitBoundsConfig == null) {
- return;
- }
-
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
- boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-
- if (enableOverviewIconMenu()) {
- ViewGroup.LayoutParams layoutParams = getLayoutParams();
- Pair<Point, Point> groupedTaskViewSizes =
- getPagedOrientationHandler()
- .getGroupedTaskViewSizes(
- deviceProfile,
- mSplitBoundsConfig,
- layoutParams.width,
- layoutParams.height
- );
-
- getPagedOrientationHandler().setSplitIconParams(mIconView.asView(), mIconView2.asView(),
- taskIconHeight, groupedTaskViewSizes.first.x, groupedTaskViewSizes.first.y,
- getLayoutParams().height, getLayoutParams().width, isRtl, deviceProfile,
- mSplitBoundsConfig);
- } else {
- getPagedOrientationHandler().setSplitIconParams(mIconView.asView(), mIconView2.asView(),
- taskIconHeight, mSnapshotView.getMeasuredWidth(),
- mSnapshotView.getMeasuredHeight(), getMeasuredHeight(), getMeasuredWidth(),
- isRtl, deviceProfile, mSplitBoundsConfig);
- }
- }
-
- private void updateSecondaryDwbPlacement() {
- if (mSecondaryTask == null) {
- return;
- }
- mDigitalWellBeingToast2.initialize(mSecondaryTask);
- }
-
- @Override
- protected void updateSnapshotRadius() {
- super.updateSnapshotRadius();
- mSnapshotView2.setFullscreenParams(mCurrentFullscreenParams);
- }
-
- @Override
- protected void setIconsAndBannersTransitionProgress(float progress, boolean invert) {
- super.setIconsAndBannersTransitionProgress(progress, invert);
- // Value set by super call
- float scale = mIconView.getAlpha();
- mIconView2.setContentAlpha(scale);
- mDigitalWellBeingToast2.updateBannerOffset(1f - scale);
- }
-
- @Override
- public void setColorTint(float amount, int tintColor) {
- super.setColorTint(amount, tintColor);
- mIconView2.setIconColorTint(tintColor, amount);
- mSnapshotView2.setDimAlpha(amount);
- mDigitalWellBeingToast2.setBannerColorTint(tintColor, amount);
- }
-
- @Override
- protected void applyThumbnailSplashAlpha() {
- super.applyThumbnailSplashAlpha();
- mSnapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha);
- }
-
- @Override
- protected void refreshTaskThumbnailSplash() {
- super.refreshTaskThumbnailSplash();
- mSnapshotView2.refreshSplashView();
- }
-
- @Override
- protected void resetViewTransforms() {
- super.resetViewTransforms();
- mSnapshotView2.resetViewTransforms();
- }
-
- /**
- * Sets visibility for thumbnails and associated elements (DWB banners).
- * IconView is unaffected.
- *
- * When setting INVISIBLE, sets the visibility for the last selected child task.
- * When setting VISIBLE (as a reset), sets the visibility for both tasks.
- */
- @Override
- void setThumbnailVisibility(int visibility, int taskId) {
- if (visibility == VISIBLE) {
- mSnapshotView.setVisibility(visibility);
- mDigitalWellBeingToast.setBannerVisibility(visibility);
- mSnapshotView2.setVisibility(visibility);
- mDigitalWellBeingToast2.setBannerVisibility(visibility);
- } else if (taskId == getTaskIds()[0]) {
- mSnapshotView.setVisibility(visibility);
- mDigitalWellBeingToast.setBannerVisibility(visibility);
- } else {
- mSnapshotView2.setVisibility(visibility);
- mDigitalWellBeingToast2.setBannerVisibility(visibility);
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
new file mode 100644
index 0000000..d6a3376
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -0,0 +1,351 @@
+/*
+ * 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.views
+
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.content.Context
+import android.graphics.PointF
+import android.util.AttributeSet
+import android.util.Log
+import android.view.View
+import com.android.internal.jank.Cuj
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.util.RunnableList
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.util.RecentsOrientedState
+import com.android.quickstep.util.SplitScreenUtils.Companion.convertLauncherSplitBoundsToShell
+import com.android.quickstep.util.SplitSelectStateController
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition
+
+/**
+ * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
+ *
+ * That's right. If you call within the next 5 minutes we'll go ahead and double your order and send
+ * you !! TWO !! Tasks along with their TaskThumbnailViews complimentary. On. The. House. And not
+ * only that, we'll even clean up your thumbnail request if you don't like it. All the benefits of
+ * one TaskView, except DOUBLED!
+ *
+ * (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included).
+ */
+class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+ TaskView(context, attrs) {
+ // TODO(b/336612373): Support new TTV for GroupedTaskView
+ var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null
+ private set
+
+ @get:PersistentSnapPosition
+ val snapPosition: Int
+ /** Returns the [PersistentSnapPosition] of this pair of tasks. */
+ get() = splitBoundsConfig?.snapPosition ?: STAGE_POSITION_UNDEFINED
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val widthSize = MeasureSpec.getSize(widthMeasureSpec)
+ val heightSize = MeasureSpec.getSize(heightMeasureSpec)
+ setMeasuredDimension(widthSize, heightSize)
+ val splitBoundsConfig = splitBoundsConfig ?: return
+ val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
+ if (initSplitTaskId == INVALID_TASK_ID) {
+ pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
+ taskContainers[0].thumbnailViewDeprecated,
+ taskContainers[1].thumbnailViewDeprecated,
+ widthSize,
+ heightSize,
+ splitBoundsConfig,
+ container.deviceProfile,
+ layoutDirection == LAYOUT_DIRECTION_RTL
+ )
+ // Should we be having a separate translation step apart from the measuring above?
+ // The following only applies to large screen for now, but for future reference
+ // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
+ // translation directions
+ taskContainers[0]
+ .thumbnailViewDeprecated
+ .applySplitSelectTranslateX(taskContainers[0].thumbnailViewDeprecated.translationX)
+ taskContainers[0]
+ .thumbnailViewDeprecated
+ .applySplitSelectTranslateY(taskContainers[0].thumbnailViewDeprecated.translationY)
+ taskContainers[1]
+ .thumbnailViewDeprecated
+ .applySplitSelectTranslateX(taskContainers[1].thumbnailViewDeprecated.translationX)
+ taskContainers[1]
+ .thumbnailViewDeprecated
+ .applySplitSelectTranslateY(taskContainers[1].thumbnailViewDeprecated.translationY)
+ } else {
+ // Currently being split with this taskView, let the non-split selected thumbnail
+ // take up full thumbnail area
+ taskContainers
+ .firstOrNull { it.task.key.id != initSplitTaskId }
+ ?.thumbnailViewDeprecated
+ ?.measure(
+ widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(
+ heightSize - container.deviceProfile.overviewTaskThumbnailTopMarginPx,
+ MeasureSpec.EXACTLY
+ )
+ )
+ }
+ if (!enableOverviewIconMenu()) {
+ updateIconPlacement()
+ }
+ }
+
+ override fun onRecycle() {
+ super.onRecycle()
+ splitBoundsConfig = null
+ }
+
+ fun bind(
+ primaryTask: Task,
+ secondaryTask: Task,
+ orientedState: RecentsOrientedState,
+ taskOverlayFactory: TaskOverlayFactory,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds?,
+ ) {
+ cancelPendingLoadTasks()
+ taskContainers =
+ listOf(
+ createTaskContainer(
+ primaryTask,
+ R.id.snapshot,
+ R.id.icon,
+ R.id.show_windows,
+ STAGE_POSITION_TOP_OR_LEFT,
+ taskOverlayFactory
+ ),
+ createTaskContainer(
+ secondaryTask,
+ R.id.bottomright_snapshot,
+ R.id.bottomRight_icon,
+ R.id.show_windows_right,
+ STAGE_POSITION_BOTTOM_OR_RIGHT,
+ taskOverlayFactory
+ )
+ )
+ this.splitBoundsConfig =
+ splitBoundsConfig?.also {
+ taskContainers[0]
+ .thumbnailViewDeprecated
+ .previewPositionHelper
+ .setSplitBounds(
+ convertLauncherSplitBoundsToShell(it),
+ PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT
+ )
+ taskContainers[1]
+ .thumbnailViewDeprecated
+ .previewPositionHelper
+ .setSplitBounds(
+ convertLauncherSplitBoundsToShell(it),
+ PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT
+ )
+ }
+ taskContainers.forEach { it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig) }
+ setOrientationState(orientedState)
+ }
+
+ override fun setOrientationState(orientationState: RecentsOrientedState) {
+ if (enableOverviewIconMenu()) {
+ splitBoundsConfig?.let {
+ val groupedTaskViewSizes =
+ orientationState.orientationHandler.getGroupedTaskViewSizes(
+ container.deviceProfile,
+ it,
+ layoutParams.width,
+ layoutParams.height
+ )
+ val iconViewMarginStart =
+ resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin
+ )
+ val iconViewBackgroundMarginStart =
+ resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_background_margin_top_start
+ )
+ 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(
+ groupedTaskViewSizes.first.x - iconMargins
+ )
+ (taskContainers[1].iconView as IconAppChipView).setMaxWidth(
+ groupedTaskViewSizes.second.x - iconMargins
+ )
+ }
+ }
+ super.setOrientationState(orientationState)
+ updateIconPlacement()
+ }
+
+ private fun updateIconPlacement() {
+ val splitBoundsConfig = splitBoundsConfig ?: return
+ val taskIconHeight = container.deviceProfile.overviewTaskIconSizePx
+ val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+ if (enableOverviewIconMenu()) {
+ val groupedTaskViewSizes =
+ pagedOrientationHandler.getGroupedTaskViewSizes(
+ container.deviceProfile,
+ splitBoundsConfig,
+ layoutParams.width,
+ layoutParams.height
+ )
+ pagedOrientationHandler.setSplitIconParams(
+ taskContainers[0].iconView.asView(),
+ taskContainers[1].iconView.asView(),
+ taskIconHeight,
+ groupedTaskViewSizes.first.x,
+ groupedTaskViewSizes.first.y,
+ layoutParams.height,
+ layoutParams.width,
+ isRtl,
+ container.deviceProfile,
+ splitBoundsConfig
+ )
+ } else {
+ pagedOrientationHandler.setSplitIconParams(
+ taskContainers[0].iconView.asView(),
+ taskContainers[1].iconView.asView(),
+ taskIconHeight,
+ taskContainers[0].thumbnailViewDeprecated.measuredWidth,
+ taskContainers[0].thumbnailViewDeprecated.measuredHeight,
+ measuredHeight,
+ measuredWidth,
+ isRtl,
+ container.deviceProfile,
+ splitBoundsConfig
+ )
+ }
+ }
+
+ fun updateSplitBoundsConfig(splitBounds: SplitConfigurationOptions.SplitBounds?) {
+ splitBoundsConfig = splitBounds
+ taskContainers.forEach {
+ it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig)
+ it.digitalWellBeingToast?.initialize(it.task)
+ }
+ invalidate()
+ }
+
+ override fun launchTaskAnimated(): RunnableList? {
+ if (taskContainers.isEmpty()) {
+ Log.d(TAG, "launchTaskAnimated - task is not bound")
+ return null
+ }
+ val recentsView = recentsView ?: return null
+ val endCallback = RunnableList()
+ // Callbacks run from remote animation when recents animation not currently running
+ InteractionJankMonitorWrapper.begin(
+ this,
+ Cuj.CUJ_SPLIT_SCREEN_ENTER,
+ "Enter form GroupedTaskView"
+ )
+ launchTaskInternal(isQuickSwitch = false, launchingExistingTaskView = true) {
+ endCallback.executeAllAndDestroy()
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER)
+ }
+
+ // Callbacks get run from recentsView for case when recents animation already running
+ recentsView.addSideTaskLaunchCallback(endCallback)
+ return endCallback
+ }
+
+ override fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) {
+ launchTaskInternal(isQuickSwitch, false, callback /*launchingExistingTaskview*/)
+ }
+
+ /**
+ * @param launchingExistingTaskView [SplitSelectStateController.launchExistingSplitPair] uses
+ * existence of GroupedTaskView as control flow of how to animate in the incoming task. If
+ * we're launching from overview (from overview thumbnails) then pass in `true`, otherwise
+ * pass in `false` for case like quickswitching from home to task
+ */
+ private fun launchTaskInternal(
+ isQuickSwitch: Boolean,
+ launchingExistingTaskView: Boolean,
+ callback: (launched: Boolean) -> Unit
+ ) {
+ recentsView?.let {
+ it.splitSelectController.launchExistingSplitPair(
+ if (launchingExistingTaskView) this else null,
+ taskContainers[0].task.key.id,
+ taskContainers[1].task.key.id,
+ STAGE_POSITION_TOP_OR_LEFT,
+ callback,
+ isQuickSwitch,
+ snapPosition
+ )
+ Log.d(TAG, "launchTaskInternal - launchExistingSplitPair: ${taskIds.contentToString()}")
+ }
+ }
+
+ /**
+ * Returns taskId that split selection was initiated with, [INVALID_TASK_ID] if no tasks in this
+ * TaskView are part of split selection
+ */
+ private fun getThisTaskCurrentlyInSplitSelection(): Int {
+ val initialTaskId = recentsView?.splitSelectController?.initialTaskId
+ return if (initialTaskId != null && containsTaskId(initialTaskId)) initialTaskId
+ else INVALID_TASK_ID
+ }
+
+ override fun getLastSelectedChildTaskIndex(): Int {
+ if (recentsView?.splitSelectController?.isDismissingFromSplitPair == true) {
+ // return the container index of the task that wasn't initially selected to split
+ // with because that is the only remaining app that can be selected. The coordinate
+ // 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
+ }
+ }
+
+ // Check which of the two apps was selected
+ if (
+ taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) ||
+ taskContainers[1].thumbnailViewDeprecated.containsPoint(lastTouchDownPosition)
+ ) {
+ return 1
+ }
+ return super.getLastSelectedChildTaskIndex()
+ }
+
+ private fun View.containsPoint(position: PointF): Boolean {
+ val localPos = floatArrayOf(position.x, position.y)
+ Utilities.mapCoordInSelfToDescendant(this, this@GroupedTaskView, localPos)
+ return Utilities.pointInView(this, localPos[0], localPos[1], 0f /* slop */)
+ }
+
+ override fun setOverlayEnabled(overlayEnabled: Boolean) {
+ if (FeatureFlags.enableAppPairs()) {
+ super.setOverlayEnabled(overlayEnabled)
+ } else {
+ // Intentional no-op to prevent setting smart actions overlay on thumbnails
+ }
+ }
+
+ companion object {
+ private const val TAG = "GroupedTaskView"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
index 1312ec3..bb4a7ec 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -28,6 +28,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.RecentsOrientedState;
@@ -37,21 +38,33 @@
* when the drawable changes.
*/
public class IconView extends View implements TaskViewIcon {
+ private static final int NUM_ALPHA_CHANNELS = 2;
+ private static final int INDEX_CONTENT_ALPHA = 0;
+ private static final int INDEX_MODAL_ALPHA = 1;
+
+ private final MultiValueAlpha mMultiValueAlpha;
@Nullable
private Drawable mDrawable;
private int mDrawableWidth, mDrawableHeight;
public IconView(Context context) {
- super(context);
+ this(context, null);
}
public IconView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
public IconView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public IconView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS);
+ mMultiValueAlpha.setUpdateVisibility(/* updateVisibility= */ true);
}
/**
@@ -143,22 +156,12 @@
@Override
public void setContentAlpha(float alpha) {
- setAlpha(alpha);
+ mMultiValueAlpha.get(INDEX_CONTENT_ALPHA).setValue(alpha);
}
@Override
public void setModalAlpha(float alpha) {
- setAlpha(alpha);
- }
-
- @Override
- public void setAlpha(float alpha) {
- super.setAlpha(alpha);
- if (alpha > 0) {
- setVisibility(VISIBLE);
- } else {
- setVisibility(INVISIBLE);
- }
+ mMultiValueAlpha.get(INDEX_MODAL_ALPHA).setValue(alpha);
}
/**
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 97f3d81..e48a7c6 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -26,7 +26,7 @@
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
import android.annotation.TargetApi;
import android.content.Context;
@@ -37,6 +37,7 @@
import androidx.annotation.Nullable;
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;
@@ -56,6 +57,8 @@
import com.android.quickstep.util.SplitSelectStateController;
import com.android.systemui.shared.recents.model.Task;
+import kotlin.Unit;
+
/**
* {@link RecentsView} used in Launcher activity
*/
@@ -73,7 +76,7 @@
public LauncherRecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr, LauncherActivityInterface.INSTANCE);
- mActivity.getStateManager().addStateListener(this);
+ getStateManager().addStateListener(this);
}
@Override
@@ -86,44 +89,50 @@
@Override
protected void handleStartHome(boolean animated) {
- StateManager stateManager = mActivity.getStateManager();
+ StateManager stateManager = getStateManager();
animated &= stateManager.shouldAnimateStateChange();
stateManager.goToState(NORMAL, animated);
if (FeatureFlags.enableSplitContextually()) {
mSplitSelectStateController.getSplitAnimationController()
- .playPlaceholderDismissAnim(mActivity, LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
+ .playPlaceholderDismissAnim(mContainer, LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
}
- AbstractFloatingView.closeAllOpenViews(mActivity, animated);
+ AbstractFloatingView.closeAllOpenViews(mContainer, animated);
}
@Override
protected boolean canStartHomeSafely() {
- return mActivity.canStartHomeSafely();
+ return mContainer.canStartHomeSafely();
}
@Override
- protected void onTaskLaunchAnimationEnd(boolean success) {
+ public StateManager<LauncherState, Launcher> getStateManager() {
+ return mContainer.getStateManager();
+ }
+
+ @Override
+ protected Unit onTaskLaunchAnimationEnd(boolean success) {
if (success) {
- mActivity.getStateManager().moveToRestState();
+ getStateManager().moveToRestState();
} else {
- LauncherState state = mActivity.getStateManager().getState();
- mActivity.getAllAppsController().setState(state);
+ LauncherState state = getStateManager().getState();
+ mContainer.getAllAppsController().setState(state);
}
super.onTaskLaunchAnimationEnd(success);
+ return Unit.INSTANCE;
}
@Override
public void onTaskIconChanged(int taskId) {
super.onTaskIconChanged(taskId);
// If Launcher needs to return to split select state, do it now, after the icon has updated.
- if (mActivity.hasPendingSplitSelectInfo()) {
- PendingSplitSelectInfo recoveryData = mActivity.getPendingSplitSelectInfo();
+ if (mContainer.hasPendingSplitSelectInfo()) {
+ PendingSplitSelectInfo recoveryData = mContainer.getPendingSplitSelectInfo();
if (recoveryData.getStagedTaskId() == taskId) {
initiateSplitSelect(
getTaskViewByTaskId(recoveryData.getStagedTaskId()),
recoveryData.getStagePosition(), recoveryData.getSource()
);
- mActivity.finishSplitSelectRecovery();
+ mContainer.finishSplitSelectRecovery();
}
}
}
@@ -138,9 +147,9 @@
@Override
public void onStateTransitionStart(LauncherState toState) {
- setOverviewStateEnabled(toState.overviewUi);
+ setOverviewStateEnabled(toState.isRecentsViewVisible);
- setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+ setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
if (toState == OVERVIEW_MODAL_TASK) {
setOverviewSelectEnabled(true);
@@ -149,13 +158,13 @@
}
// Set border after select mode changes to avoid showing border during state transition
- if (!toState.overviewUi || toState == OVERVIEW_MODAL_TASK) {
+ if (!toState.isRecentsViewVisible || toState == OVERVIEW_MODAL_TASK) {
setTaskBorderEnabled(false);
}
setFreezeViewVisibility(true);
- if (mActivity.getDesktopVisibilityController() != null) {
- mActivity.getDesktopVisibilityController().onLauncherStateChanged(toState);
+ if (mContainer.getDesktopVisibilityController() != null) {
+ mContainer.getDesktopVisibilityController().onLauncherStateChanged(toState);
}
}
@@ -173,7 +182,7 @@
setOverviewSelectEnabled(false);
}
- if (finalState.overviewUi && finalState != OVERVIEW_MODAL_TASK) {
+ if (finalState.isRecentsViewVisible && finalState != OVERVIEW_MODAL_TASK) {
setTaskBorderEnabled(true);
}
@@ -187,8 +196,8 @@
public void setOverviewStateEnabled(boolean enabled) {
super.setOverviewStateEnabled(enabled);
if (enabled) {
- LauncherState state = mActivity.getStateManager().getState();
- boolean hasClearAllButton = (state.getVisibleElements(mActivity)
+ LauncherState state = getStateManager().getState();
+ boolean hasClearAllButton = (state.getVisibleElements(mContainer)
& CLEAR_ALL_BUTTON) != 0;
setDisallowScrollToClearAll(!hasClearAllButton);
}
@@ -198,30 +207,28 @@
public boolean onTouchEvent(MotionEvent ev) {
boolean result = super.onTouchEvent(ev);
// Do not let touch escape to siblings below this view.
- return result || mActivity.getStateManager().getState().overviewUi;
+ return result || getStateManager().getState().isRecentsViewVisible;
}
@Override
protected DepthController getDepthController() {
- return mActivity.getDepthController();
+ return mContainer.getDepthController();
}
@Override
public void setModalStateEnabled(int taskId, boolean animate) {
if (taskId != INVALID_TASK_ID) {
setSelectedTask(taskId);
- mActivity.getStateManager().goToState(LauncherState.OVERVIEW_MODAL_TASK, animate);
- } else {
- if (mActivity.isInState(LauncherState.OVERVIEW_MODAL_TASK)) {
- mActivity.getStateManager().goToState(LauncherState.OVERVIEW, animate);
- }
+ getStateManager().goToState(LauncherState.OVERVIEW_MODAL_TASK, animate);
+ } else if (mContainer.isInState(LauncherState.OVERVIEW_MODAL_TASK)) {
+ getStateManager().goToState(LauncherState.OVERVIEW, animate);
}
}
@Override
protected void onDismissAnimationEnds() {
super.onDismissAnimationEnds();
- if (mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ if (mContainer.isInState(OVERVIEW_SPLIT_SELECT)) {
// We want to keep the tasks translations in this temporary state
// after resetting the rest above
setTaskViewsPrimarySplitTranslation(mTaskViewsPrimarySplitTranslation);
@@ -234,13 +241,13 @@
@SplitConfigurationOptions.StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
super.initiateSplitSelect(taskView, stagePosition, splitEvent);
- mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
+ getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
}
@Override
public void initiateSplitSelect(SplitSelectSource splitSelectSource) {
super.initiateSplitSelect(splitSelectSource);
- mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
+ getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
}
@Override
@@ -248,7 +255,7 @@
if (FeatureFlags.enableSplitContextually()) {
return !mSplitSelectStateController.isSplitSelectActive();
} else {
- return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
+ return !mContainer.isInState(OVERVIEW_SPLIT_SELECT);
}
}
@@ -257,33 +264,36 @@
RotationTouchHelper rotationTouchHelper) {
super.onGestureAnimationStart(runningTasks, rotationTouchHelper);
DesktopVisibilityController desktopVisibilityController =
- mActivity.getDesktopVisibilityController();
- if (desktopVisibilityController != null) {
+ mContainer.getDesktopVisibilityController();
+ if (!enableDesktopWindowingWallpaperActivity() && desktopVisibilityController != null) {
+ // TODO: b/333533253 - Remove after flag rollout
desktopVisibilityController.setRecentsGestureStart();
}
}
@Override
public void onGestureAnimationEnd() {
- DesktopVisibilityController desktopVisibilityController = null;
+ DesktopVisibilityController desktopVisibilityController =
+ mContainer.getDesktopVisibilityController();
boolean showDesktopApps = false;
GestureState.GestureEndTarget endTarget = null;
- if (isDesktopModeSupported()) {
- desktopVisibilityController = mActivity.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ desktopVisibilityController = mContainer.getDesktopVisibilityController();
endTarget = mCurrentGestureEndTarget;
if (endTarget == GestureState.GestureEndTarget.LAST_TASK
- && desktopVisibilityController.areFreeformTasksVisible()) {
+ && desktopVisibilityController.areDesktopTasksVisible()) {
// Recents gesture was cancelled and we are returning to the previous task.
// After super class has handled clean up, show desktop apps on top again
showDesktopApps = true;
}
}
super.onGestureAnimationEnd();
- if (desktopVisibilityController != null) {
+ if (!enableDesktopWindowingWallpaperActivity() && desktopVisibilityController != null) {
+ // TODO: b/333533253 - Remove after flag rollout
desktopVisibilityController.setRecentsGestureEnd(endTarget);
}
if (showDesktopApps) {
- SystemUiProxy.INSTANCE.get(mActivity).showDesktopApps(mActivity.getDisplayId(),
+ SystemUiProxy.INSTANCE.get(mContainer).showDesktopApps(mContainer.getDisplayId(),
null /* transition */);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 8281ad7..d9468c7 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -25,6 +25,7 @@
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -33,8 +34,8 @@
import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
@@ -43,14 +44,13 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
-import java.util.stream.Collectors;
/**
* View for showing action buttons in Overview
*/
public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
implements OnClickListener, Insettable {
-
+ public static final String TAG = "OverviewActionsView";
private final Rect mInsets = new Rect();
@IntDef(flag = true, value = {
@@ -90,16 +90,34 @@
private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
private static final int INDEX_SHARE_TARGET_ALPHA = 4;
private static final int INDEX_SCROLL_ALPHA = 5;
- private static final int NUM_ALPHAS = 6;
+ private static final int INDEX_GROUPED_ALPHA = 6;
+ private static final int INDEX_3P_LAUNCHER = 7;
+ private static final int NUM_ALPHAS = 8;
public @interface SplitButtonHiddenFlags { }
- public static final int FLAG_IS_NOT_TABLET = 1 << 0;
+ public static final int FLAG_SMALL_SCREEN_HIDE_SPLIT = 1 << 0;
- public @interface SplitButtonDisabledFlags { }
- public static final int FLAG_SINGLE_TASK = 1 << 0;
+ /**
+ * Holds an AnimatedFloat for each alpha property, used to set or animate alpha values in
+ * {@link #mMultiValueAlphas}.
+ */
+ private final AnimatedFloat[] mAlphaProperties = new AnimatedFloat[NUM_ALPHAS];
- private MultiValueAlpha mMultiValueAlpha;
+ /** Holds MultiValueAlpha values for all actions bars */
+ private final MultiValueAlpha[] mMultiValueAlphas = new MultiValueAlpha[2];
+ /** Index used for single-task actions in the mMultiValueAlphas array */
+ private static final int ACTIONS_ALPHAS = 0;
+ /** Index used for grouped-task actions in the mMultiValueAlphas array */
+ private static final int GROUP_ACTIONS_ALPHAS = 1;
+
+ /** Container for the action buttons below a focused, non-split Overview tile. */
+ protected LinearLayout mActionButtons;
private Button mSplitButton;
+ /**
+ * The "save app pair" button. Currently this is the only button that is not contained in
+ * mActionButtons, since it is the sole button that appears for a grouped task.
+ */
+ private Button mSaveAppPairButton;
@ActionsHiddenFlags
private int mHiddenFlags;
@@ -110,15 +128,14 @@
@SplitButtonHiddenFlags
private int mSplitButtonHiddenFlags;
- @SplitButtonDisabledFlags
- private int mSplitButtonDisabledFlags;
-
@Nullable
protected T mCallbacks;
@Nullable
protected DeviceProfile mDp;
private final Rect mTaskSize = new Rect();
+ private boolean mIsGroupedTask = false;
+ private boolean mCanSaveAppPair = false;
public OverviewActionsView(Context context) {
this(context, null);
@@ -135,12 +152,35 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mMultiValueAlpha = new MultiValueAlpha(findViewById(R.id.action_buttons), NUM_ALPHAS);
- mMultiValueAlpha.setUpdateVisibility(true);
+ // Initialize 2 view containers: one for single tasks, one for grouped tasks.
+ // These will take up the same space on the screen and alternate visibility as needed.
+ // Currently, the only grouped task action is "save app pairs".
+ mActionButtons = findViewById(R.id.action_buttons);
+ mSaveAppPairButton = findViewById(R.id.action_save_app_pair);
+ // Initialize a list to hold alphas for mActionButtons and any group action buttons.
+ mMultiValueAlphas[ACTIONS_ALPHAS] = new MultiValueAlpha(mActionButtons, NUM_ALPHAS);
+ mMultiValueAlphas[GROUP_ACTIONS_ALPHAS] =
+ new MultiValueAlpha(mSaveAppPairButton, NUM_ALPHAS);
+ Arrays.stream(mMultiValueAlphas).forEach(a -> a.setUpdateVisibility(true));
+ // To control alpha simultaneously on mActionButtons and any group action buttons, we set up
+ // an AnimatedFloat for each alpha property.
+ for (int i = 0; i < NUM_ALPHAS; i++) {
+ final int index = i;
+ mAlphaProperties[index] = new AnimatedFloat(() -> {
+ for (MultiValueAlpha multiValueAlpha : mMultiValueAlphas) {
+ multiValueAlpha.get(index).setValue(mAlphaProperties[index].value);
+ }
+ }, 1f /* initialValue */);
+ }
- findViewById(R.id.action_screenshot).setOnClickListener(this);
+ // The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is
+ // an ImageButton in go launcher (does not share a common class with Button). Take care when
+ // casting this.
+ View screenshotButton = findViewById(R.id.action_screenshot);
+ screenshotButton.setOnClickListener(this);
mSplitButton = findViewById(R.id.action_split);
mSplitButton.setOnClickListener(this);
+ mSaveAppPairButton.setOnClickListener(this);
}
/**
@@ -162,6 +202,8 @@
mCallbacks.onScreenshot();
} else if (id == R.id.action_split) {
mCallbacks.onSplit();
+ } else if (id == R.id.action_save_app_pair) {
+ mCallbacks.onSaveAppPair();
}
}
@@ -185,7 +227,7 @@
mHiddenFlags &= ~visibilityFlags;
}
boolean isHidden = mHiddenFlags != 0;
- mMultiValueAlpha.get(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1);
+ mAlphaProperties[INDEX_HIDDEN_FLAGS_ALPHA].updateValue(isHidden ? 0 : 1);
}
/**
@@ -204,7 +246,56 @@
}
boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
LayoutUtils.setViewEnabled(this, isEnabled);
- updateSplitButtonEnabledState();
+ }
+
+ /**
+ * Updates a batch of flags to hide and show actions buttons when a grouped task (split screen)
+ * is focused.
+ * @param isGroupedTask True if the focused task is a grouped task.
+ * @param canSaveAppPair True if the focused task is a grouped task and can be saved as an app
+ * pair.
+ */
+ public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) {
+ Log.d(TAG, "updateForGroupedTask() called with: isGroupedTask = [" + isGroupedTask
+ + "], canSaveAppPair = [" + canSaveAppPair + "]");
+ mIsGroupedTask = isGroupedTask;
+ mCanSaveAppPair = canSaveAppPair;
+ updateActionButtonsVisibility();
+ }
+
+ /**
+ * Updates a batch of flags to hide and show actions buttons for tablet/non tablet case.
+ */
+ private void updateForIsTablet() {
+ assert mDp != null;
+ // Update flags to see if split button should be hidden.
+ updateSplitButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_SPLIT, !mDp.isTablet);
+ updateActionButtonsVisibility();
+ }
+
+ private void updateActionButtonsVisibility() {
+ assert mDp != null;
+ boolean showSingleTaskActions = !mIsGroupedTask;
+ boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair;
+ Log.d(TAG, "updateActionButtonsVisibility() called: showSingleTaskActions = ["
+ + showSingleTaskActions + "], showGroupActions = [" + showGroupActions + "]");
+ getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0);
+ getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0);
+ }
+
+ /**
+ * Updates flags to hide and show actions buttons for 1p/3p launchers.
+ */
+ public void updateFor3pLauncher(boolean is3pLauncher) {
+ getGroupActionsAlphas().get(INDEX_3P_LAUNCHER).setValue(is3pLauncher ? 0 : 1);
+ }
+
+ private MultiValueAlpha getActionsAlphas() {
+ return mMultiValueAlphas[ACTIONS_ALPHAS];
+ }
+
+ private MultiValueAlpha getGroupActionsAlphas() {
+ return mMultiValueAlphas[GROUP_ACTIONS_ALPHAS];
}
/**
@@ -213,69 +304,47 @@
* @param flag The flag to update.
* @param enable Whether to enable the hidden flag: True will cause view to be hidden.
*/
- public void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag, boolean enable) {
+ void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag,
+ boolean enable) {
+ if (mSplitButton == null) return;
if (enable) {
mSplitButtonHiddenFlags |= flag;
} else {
mSplitButtonHiddenFlags &= ~flag;
}
- if (mSplitButton == null) return;
- boolean shouldBeVisible = mSplitButtonHiddenFlags == 0;
- mSplitButton.setVisibility(shouldBeVisible ? VISIBLE : GONE);
- findViewById(R.id.action_split_space).setVisibility(shouldBeVisible ? VISIBLE : GONE);
-
- String callStack = Arrays.stream(
- Log.getStackTraceString(new Exception("thread stacktrace"))
- .split("\\n"))
- .limit(5)
- .skip(1) // Removes the line "java.lang.Exception: thread stacktrace"
- .collect(Collectors.joining("\n"));
- Log.d("b/321291049", "updateSplitButtonHiddenFlags called with flag: " + flag
- + " enabled: " + enable
- + " shouldBeVisible: " + shouldBeVisible
- + " partial trace: \n" + callStack);
- }
-
- /**
- * Updates the proper flags to indicate whether the "Split screen" button should be disabled.
- *
- * @param flag The flag to update.
- * @param enable Whether to enable the disable flag: True will cause view to be disabled.
- */
- public void updateSplitButtonDisabledFlags(@SplitButtonDisabledFlags int flag, boolean enable) {
- if (enable) {
- mSplitButtonDisabledFlags |= flag;
- } else {
- mSplitButtonDisabledFlags &= ~flag;
+ int desiredVisibility = mSplitButtonHiddenFlags == 0 ? VISIBLE : GONE;
+ if (mSplitButton.getVisibility() != desiredVisibility) {
+ mSplitButton.setVisibility(desiredVisibility);
+ mActionButtons.requestLayout();
}
- updateSplitButtonEnabledState();
}
- public MultiProperty getContentAlpha() {
- return mMultiValueAlpha.get(INDEX_CONTENT_ALPHA);
+ public AnimatedFloat getContentAlpha() {
+ return mAlphaProperties[INDEX_CONTENT_ALPHA];
}
- public MultiProperty getVisibilityAlpha() {
- return mMultiValueAlpha.get(INDEX_VISIBILITY_ALPHA);
+ public AnimatedFloat getVisibilityAlpha() {
+ return mAlphaProperties[INDEX_VISIBILITY_ALPHA];
}
- public MultiProperty getFullscreenAlpha() {
- return mMultiValueAlpha.get(INDEX_FULLSCREEN_ALPHA);
+ public AnimatedFloat getFullscreenAlpha() {
+ return mAlphaProperties[INDEX_FULLSCREEN_ALPHA];
}
- public MultiProperty getShareTargetAlpha() {
- return mMultiValueAlpha.get(INDEX_SHARE_TARGET_ALPHA);
+ public AnimatedFloat getShareTargetAlpha() {
+ return mAlphaProperties[INDEX_SHARE_TARGET_ALPHA];
}
- public MultiProperty getIndexScrollAlpha() {
- return mMultiValueAlpha.get(INDEX_SCROLL_ALPHA);
+ public AnimatedFloat getIndexScrollAlpha() {
+ return mAlphaProperties[INDEX_SCROLL_ALPHA];
}
/**
* Returns the visibility of the overview actions buttons.
*/
- public @Visibility int getActionsButtonVisibility() {
- return findViewById(R.id.action_buttons).getVisibility();
+ public boolean areActionsButtonsVisible() {
+ return mActionButtons.getVisibility() == View.VISIBLE
+ || mSaveAppPairButton.getVisibility() == View.VISIBLE;
}
/**
@@ -288,11 +357,17 @@
/** Updates vertical margins for different navigation mode or configuration changes. */
public void updateVerticalMargin(NavigationMode mode) {
+ updateActionBarPosition(mActionButtons);
+ updateActionBarPosition(mSaveAppPairButton);
+ }
+
+ /** Positions actions buttons according to device settings and insets. */
+ private void updateActionBarPosition(View actionBar) {
if (mDp == null) {
return;
}
- LayoutParams actionParams = (LayoutParams) findViewById(
- R.id.action_buttons).getLayoutParams();
+
+ LayoutParams actionParams = (LayoutParams) actionBar.getLayoutParams();
actionParams.setMargins(
actionParams.leftMargin, mDp.overviewActionsTopMarginPx,
actionParams.rightMargin, getBottomMargin());
@@ -319,6 +394,7 @@
mDp = dp;
mTaskSize.set(taskSize);
updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
+ updateForIsTablet();
requestLayout();
@@ -326,19 +402,11 @@
? R.drawable.ic_split_horizontal
: R.drawable.ic_split_vertical;
mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds(splitIconRes, 0, 0, 0);
- }
- /**
- * Enables/disables the "Split" button based on the status of mSplitButtonDisabledFlags and
- * mDisabledFlags.
- */
- private void updateSplitButtonEnabledState() {
- if (mSplitButton == null) {
- return;
- }
- boolean isParentEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
- boolean shouldBeEnabled = mSplitButtonDisabledFlags == 0 && isParentEnabled;
- mSplitButton.setEnabled(shouldBeEnabled);
+ int appPairIconRes = dp.isLeftRightSplit
+ ? R.drawable.ic_save_app_pair_left_right
+ : R.drawable.ic_save_app_pair_up_down;
+ mSaveAppPairButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ appPairIconRes, 0, 0, 0);
}
-
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e516870..d7c7857 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -31,10 +31,13 @@
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.app.animation.Interpolators.OVERSHOOT_0_75;
import static com.android.app.animation.Interpolators.clampToProgress;
+import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
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.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
@@ -44,6 +47,7 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ORIENTATION_CHANGED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
@@ -61,9 +65,6 @@
import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_TAB;
import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_UP;
import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
-import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
-import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
@@ -131,9 +132,10 @@
import androidx.core.graphics.ColorUtils;
import com.android.internal.jank.Cuj;
-import com.android.launcher3.BaseActivity;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.PagedView;
@@ -147,11 +149,13 @@
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
+import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.BaseState;
-import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.OverScroll;
@@ -165,10 +169,11 @@
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.TranslateEdgeEffect;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.ViewPool;
-import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.RecentsAnimationController;
@@ -187,6 +192,8 @@
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.ViewUtils;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.recents.data.TasksRepository;
+import com.android.quickstep.recents.viewmodel.RecentsViewData;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AnimUtils;
@@ -205,7 +212,7 @@
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.VibrationConstants;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -214,7 +221,11 @@
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.common.pip.IPipAnimationListener;
+import com.android.wm.shell.shared.DesktopModeStatus;
+
+import kotlin.Unit;
import java.util.ArrayList;
import java.util.Arrays;
@@ -228,8 +239,11 @@
/**
* A list of recent tasks.
+ * @param <CONTAINER_TYPE> : the container that should host recents view
+ * @param <STATE_TYPE> : the type of base state that will be used
*/
-public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>,
+
+public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewContainer,
STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable,
TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
TaskVisualsChangeListener {
@@ -297,6 +311,7 @@
public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f;
public static final VibrationEffect SCROLL_VIBRATION_FALLBACK =
VibrationConstants.EFFECT_TEXTURE_TICK;
+ public static final int UNBOUND_TASK_VIEW_ID = -1;
/**
* Can be used to tint the color of the RecentsView to simulate a scrim that can views
@@ -374,6 +389,9 @@
public void setValue(RecentsView view, float scale) {
view.setScaleX(scale);
view.setScaleY(scale);
+ if (enableRefactorTaskThumbnail()) {
+ view.mRecentsViewData.getScale().setValue(scale);
+ }
view.mLastComputedTaskStartPushOutDistance = null;
view.mLastComputedTaskEndPushOutDistance = null;
view.runActionOnRemoteHandles(new Consumer<RemoteTargetHandle>() {
@@ -444,8 +462,11 @@
private static final float FOREGROUND_SCRIM_TINT = 0.32f;
+ public final RecentsViewData mRecentsViewData = new RecentsViewData();
+ public final TasksRepository mTasksRepository;
+
protected final RecentsOrientedState mOrientationState;
- protected final BaseActivityInterface<STATE_TYPE, ACTIVITY_TYPE> mSizeStrategy;
+ protected final BaseContainerInterface<STATE_TYPE, CONTAINER_TYPE> mSizeStrategy;
@Nullable
protected RecentsAnimationController mRecentsAnimationController;
@Nullable
@@ -465,7 +486,6 @@
protected final Rect mLastComputedTaskSize = new Rect();
protected final Rect mLastComputedGridSize = new Rect();
protected final Rect mLastComputedGridTaskSize = new Rect();
- protected final Rect mLastComputedDesktopTaskSize = new Rect();
private TaskView mSelectedTask = null;
// How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
@Nullable
@@ -483,7 +503,7 @@
// The threshold at which we update the SystemUI flags when animating from the task into the app
public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f;
- protected final ACTIVITY_TYPE mActivity;
+ protected final CONTAINER_TYPE mContainer;
private final float mFastFlingVelocity;
private final int mScrollHapticMinGapMillis;
private final RecentsModel mModel;
@@ -590,7 +610,7 @@
if (taskView == null) {
return;
}
- Task.TaskKey taskKey = taskView.getTask().key;
+ Task.TaskKey taskKey = taskView.getFirstTask().key;
UI_HELPER_EXECUTOR.execute(new CancellableTask<>(
() -> PackageManagerWrapper.getInstance()
.getActivityInfo(taskKey.getComponent(), taskKey.userId) == null,
@@ -625,7 +645,6 @@
*/
protected int mRunningTaskViewId = -1;
private int mTaskViewIdCount;
- private final int[] INVALID_TASK_IDS = new int[]{-1, -1};
protected boolean mRunningTaskTileHidden;
@Nullable
private Task[] mTmpRunningTasks;
@@ -723,6 +742,11 @@
private int mSplitHiddenTaskViewIndex = -1;
@Nullable
private FloatingTaskView mSecondFloatingTaskView;
+ /**
+ * A fullscreen scrim that goes behind the splitscreen animation to hide color conflicts and
+ * possible flickers. Removed after tasks + divider finish animating in.
+ */
+ private View mSplitScrim;
/**
* The task to be removed and immediately re-added. Should not be added to task pool.
@@ -769,15 +793,17 @@
// keeps track of the state of the filter for tasks in recents view
private final RecentsFilterState mFilterState = new RecentsFilterState();
+ private int mOffsetMidpointIndexOverride = INVALID_PAGE;
+
public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- BaseActivityInterface sizeStrategy) {
+ BaseContainerInterface sizeStrategy) {
super(context, attrs, defStyleAttr);
setEnableFreeScroll(true);
mSizeStrategy = sizeStrategy;
- mActivity = BaseActivity.fromContext(context);
+ mContainer = RecentsViewContainer.containerFromContext(context);
mOrientationState = new RecentsOrientedState(
context, mSizeStrategy, this::animateRecentsRotationInPlace);
- final int rotation = mActivity.getDisplay().getRotation();
+ final int rotation = mContainer.getDisplay().getRotation();
mOrientationState.setRecentsRotation(rotation);
mScrollHapticMinGapMillis = getResources()
@@ -786,6 +812,12 @@
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mModel = RecentsModel.INSTANCE.get(context);
mIdp = InvariantDeviceProfile.INSTANCE.get(context);
+ if (enableRefactorTaskThumbnail()) {
+ mTasksRepository = new TasksRepository(
+ mModel, mModel.getThumbnailCache(), mModel.getIconCache());
+ } else {
+ mTasksRepository = null;
+ }
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
.inflate(R.layout.overview_clear_all_button, this, false);
@@ -829,7 +861,7 @@
R.string.task_overlay_factory_class);
// Initialize quickstep specific cache params here, as this is constructed only once
- mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
+ mContainer.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
mTintingColor = getForegroundScrimDimColor(context);
@@ -837,9 +869,9 @@
if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) {
// invalidate the current list of tasks if filter changes with a fading in/out animation
mFilterState.setOnFilterUpdatedListener(() -> {
- Animator animatorFade = mActivity.getStateManager().createStateElementAnimation(
+ Animator animatorFade = getStateManager().createStateElementAnimation(
RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM, 1f, 0f);
- Animator animatorAppear = mActivity.getStateManager().createStateElementAnimation(
+ Animator animatorAppear = getStateManager().createStateElementAnimation(
RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM, 0f, 1f);
animatorFade.addListener(new AnimatorListenerAdapter() {
@Override
@@ -988,12 +1020,12 @@
if (mHandleTaskStackChanges) {
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView != null) {
- for (TaskIdAttributeContainer container :
- taskView.getTaskIdAttributeContainers()) {
+ for (TaskContainer container : taskView.getTaskContainers()) {
if (container == null || taskId != container.getTask().key.id) {
continue;
}
- container.getThumbnailView().setThumbnail(container.getTask(), thumbnailData);
+ container.getThumbnailViewDeprecated().setThumbnail(container.getTask(),
+ thumbnailData);
}
}
}
@@ -1004,11 +1036,11 @@
public void onTaskIconChanged(String pkg, UserHandle user) {
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView tv = requireTaskViewAt(i);
- Task task = tv.getTask();
- if (task != null && task.key != null && pkg.equals(task.key.getPackageName())
- && task.key.userId == user.getIdentifier()) {
+ Task task = tv.getFirstTask();
+ if (pkg.equals(task.key.getPackageName()) && task.key.userId == user.getIdentifier()) {
task.icon = null;
- if (tv.getIconView().getDrawable() != null) {
+ if (tv.getTaskContainers().stream().anyMatch(
+ container -> container.getIconView().getDrawable() != null)) {
tv.onTaskListVisibilityChanged(true /* visible */);
}
}
@@ -1039,13 +1071,14 @@
continue;
}
// taskView could be a GroupedTaskView, so select the relevant task by ID
- TaskIdAttributeContainer taskAttributes = taskView.getTaskAttributesById(id);
+ TaskContainer taskAttributes = taskView.getTaskContainerById(id);
if (taskAttributes == null) {
continue;
}
Task task = taskAttributes.getTask();
- TaskThumbnailView taskThumbnailView = taskAttributes.getThumbnailView();
- taskThumbnailView.setThumbnail(task, thumbnail, refreshNow);
+ TaskThumbnailViewDeprecated taskThumbnailViewDeprecated =
+ taskAttributes.getThumbnailViewDeprecated();
+ taskThumbnailViewDeprecated.setThumbnail(task, thumbnail, refreshNow);
// thumbnailData can contain 1-2 ids, but they should correspond to the same
// TaskView, so overwriting is ok
updatedTaskView = taskView;
@@ -1064,6 +1097,8 @@
@Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
mActionsView = actionsView;
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+ // Update flags for 1p/3p launchers
+ mActionsView.updateFor3pLauncher(!supportsAppPairs());
mSplitSelectStateController = splitController;
mDesktopRecentsTransitionController = desktopRecentsTransitionController;
}
@@ -1089,13 +1124,13 @@
super.onAttachedToWindow();
updateTaskStackListenerState();
mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
- mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
+ mContainer.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
mSyncTransactionApplier = new SurfaceTransactionApplier(this);
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
.setSyncTransactionApplier(mSyncTransactionApplier));
RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
- mIPipAnimationListener.setActivityAndRecentsView(mActivity, this);
+ mIPipAnimationListener.setActivityAndRecentsView(mContainer, this);
SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(
mIPipAnimationListener);
mOrientationState.initListeners();
@@ -1110,7 +1145,7 @@
super.onDetachedFromWindow();
updateTaskStackListenerState();
mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this);
- mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
+ mContainer.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
mSyncTransactionApplier = null;
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
@@ -1124,6 +1159,7 @@
if (FeatureFlags.enableSplitContextually()) {
mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener);
}
+ reset();
}
@Override
@@ -1147,7 +1183,6 @@
} else {
mTaskViewPool.recycle(taskView);
}
- taskView.setTaskViewId(-1);
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
}
}
@@ -1250,20 +1285,27 @@
if (taskView == null || !isTaskViewVisible(taskView)) {
// TODO: Refine this animation.
SurfaceTransactionApplier surfaceApplier =
- new SurfaceTransactionApplier(mActivity.getDragLayer());
+ new SurfaceTransactionApplier(mContainer.getDragLayer());
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
appAnimator.setInterpolator(ACCELERATE_DECELERATE);
+ final Matrix matrix = new Matrix();
appAnimator.addUpdateListener(valueAnimator -> {
float percent = valueAnimator.getAnimatedFraction();
SurfaceTransaction transaction = new SurfaceTransaction();
- Matrix matrix = new Matrix();
- matrix.postScale(percent, percent);
- matrix.postTranslate(mActivity.getDeviceProfile().widthPx * (1 - percent) / 2,
- mActivity.getDeviceProfile().heightPx * (1 - percent) / 2);
- transaction.forSurface(apps[apps.length - 1].leash)
- .setAlpha(percent)
- .setMatrix(matrix);
+ for (int i = apps.length - 1; i >= 0; --i) {
+ RemoteAnimationTarget app = apps[i];
+
+ float dx = mContainer.getDeviceProfile().widthPx * (1 - percent) / 2
+ + app.screenSpaceBounds.left * percent;
+ float dy = mContainer.getDeviceProfile().heightPx * (1 - percent) / 2
+ + app.screenSpaceBounds.top * percent;
+ matrix.setScale(percent, percent);
+ matrix.postTranslate(dx, dy);
+ transaction.forSurface(app.leash)
+ .setAlpha(percent)
+ .setMatrix(matrix);
+ }
surfaceApplier.scheduleApply(transaction);
});
appAnimator.addListener(new AnimatorListenerAdapter() {
@@ -1273,6 +1315,8 @@
final SurfaceTransaction showTransaction = new SurfaceTransaction();
for (int i = apps.length - 1; i >= 0; --i) {
showTransaction.getTransaction().show(apps[i].leash);
+ showTransaction.forSurface(apps[i].leash).setLayer(
+ Integer.MAX_VALUE - 1000 + apps[i].prefixOrderIndex);
}
surfaceApplier.scheduleApply(showTransaction);
}
@@ -1286,7 +1330,7 @@
});
} else {
TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps,
- true /* launcherClosing */, mActivity.getStateManager(), this,
+ true /* launcherClosing */, getStateManager(), this,
getDepthController());
}
anim.start();
@@ -1399,7 +1443,7 @@
*/
@Nullable
public TaskView getTaskViewByTaskIds(int[] taskIds) {
- if (!hasAnyValidTaskIds(taskIds)) {
+ if (!hasAllValidTaskIds(taskIds)) {
return null;
}
@@ -1418,9 +1462,11 @@
return null;
}
- /** Returns false if {@code taskIds} is null or contains invalid values, true otherwise */
- private boolean hasAnyValidTaskIds(int[] taskIds) {
- return taskIds != null && !Arrays.equals(taskIds, INVALID_TASK_IDS);
+ /** Returns false if {@code taskIds} is null or contains any invalid values, true otherwise */
+ private boolean hasAllValidTaskIds(int[] taskIds) {
+ return taskIds != null
+ && taskIds.length > 0
+ && Arrays.stream(taskIds).noneMatch(taskId -> taskId == INVALID_TASK_ID);
}
public void setOverviewStateEnabled(boolean enabled) {
@@ -1446,6 +1492,7 @@
TaskView taskView = requireTaskViewAt(i);
taskView.setBorderEnabled(enabled);
}
+ mClearAllButton.setBorderEnabled(enabled);
}
/**
@@ -1462,7 +1509,7 @@
@Override
protected void onPageBeginTransition() {
super.onPageBeginTransition();
- if (!mActivity.getDeviceProfile().isTablet) {
+ if (!mContainer.getDeviceProfile().isTablet) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
}
if (mOverviewStateEnabled) { // only when in overview
@@ -1475,7 +1522,7 @@
super.onPageEndTransition();
ActiveGestureLog.INSTANCE.addLog(
"onPageEndTransition: current page index updated", getNextPage());
- if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
+ if (isClearAllHidden() && !mContainer.getDeviceProfile().isTablet) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
}
if (getNextPage() > 0) {
@@ -1486,7 +1533,7 @@
@Override
protected boolean isSignificantMove(float absoluteDelta, int pageOrientedSize) {
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ DeviceProfile deviceProfile = mContainer.getDeviceProfile();
if (!deviceProfile.isTablet) {
return super.isSignificantMove(absoluteDelta, pageOrientedSize);
}
@@ -1496,6 +1543,15 @@
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ boolean intercept = super.onInterceptTouchEvent(ev);
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ Log.d("b/318590728", "onInterceptTouchEvent: " + ev);
+ }
+ return intercept;
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
@@ -1635,7 +1691,7 @@
* required to move the running task in grid.
*/
public void moveRunningTaskToFront() {
- if (!mActivity.getDeviceProfile().isTablet) {
+ if (!mContainer.getDeviceProfile().isTablet) {
return;
}
@@ -1660,15 +1716,8 @@
removeView(runningTaskView);
mMovingTaskView = null;
runningTaskView.resetPersistentViewTransforms();
- int frontTaskIndex = 0;
- if (isDesktopModeSupported() && mDesktopTaskView != null
- && !runningTaskView.isDesktopTask()) {
- // If desktop mode is enabled, desktop task view is pinned at first position if present.
- // Move running task to position 1.
- frontTaskIndex = 1;
- }
- addView(runningTaskView, frontTaskIndex);
- setCurrentPage(frontTaskIndex);
+ addView(runningTaskView, 0);
+ setCurrentPage(0);
updateTaskSize();
}
@@ -1684,12 +1733,18 @@
return super.isPageScrollsInitialized() && mLoadPlanEverApplied;
}
- protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {
+ protected void applyLoadPlan(List<GroupTask> taskGroups) {
if (mPendingAnimation != null) {
mPendingAnimation.addEndListener(success -> applyLoadPlan(taskGroups));
return;
}
+ if (taskGroups == null) {
+ Log.d(TAG, "applyLoadPlan - taskGroups is null");
+ } else {
+ Log.d(TAG, "applyLoadPlan - taskGroups: " + taskGroups.stream().map(
+ GroupTask::toString).toList());
+ }
mLoadPlanEverApplied = true;
if (taskGroups == null || taskGroups.isEmpty()) {
removeTasksViewsAndClearAllButton();
@@ -1703,10 +1758,12 @@
return;
}
- int[] currentTaskId = INVALID_TASK_IDS;
+ int[] currentTaskIds;
TaskView currentTaskView = getTaskViewAt(mCurrentPage);
- if (currentTaskView != null && currentTaskView.getTask() != null) {
- currentTaskId = currentTaskView.getTaskIds();
+ if (currentTaskView != null) {
+ currentTaskIds = currentTaskView.getTaskIds();
+ } else {
+ currentTaskIds = new int[0];
}
// Unload existing visible task data
@@ -1718,8 +1775,8 @@
// Save running task ID if it exists before rebinding all taskViews, otherwise the task from
// the runningTaskView currently bound could get assigned to another TaskView
- int[] runningTaskId = getTaskIdsForTaskViewId(mRunningTaskViewId);
- int[] focusedTaskId = getTaskIdsForTaskViewId(mFocusedTaskViewId);
+ int[] runningTaskIds = getTaskIdsForTaskViewId(mRunningTaskViewId);
+ int[] focusedTaskIds = getTaskIdsForTaskViewId(mFocusedTaskViewId);
// Reset the focused task to avoiding initializing TaskViews layout as focused task during
// binding. The focused task view will be updated after all the TaskViews are bound.
@@ -1733,66 +1790,53 @@
// If we are entering Overview as a result of initiating a split from somewhere else
// (e.g. split from Home), we need to make sure the staged app is not drawn as a thumbnail.
- int stagedTaskIdToBeRemovedFromGrid;
+ int stagedTaskIdToBeRemoved;
if (isSplitSelectionActive()) {
- stagedTaskIdToBeRemovedFromGrid = mSplitSelectStateController.getInitialTaskId();
+ stagedTaskIdToBeRemoved = mSplitSelectStateController.getInitialTaskId();
updateCurrentTaskActionsVisibility();
} else {
- stagedTaskIdToBeRemovedFromGrid = INVALID_TASK_ID;
+ stagedTaskIdToBeRemoved = INVALID_TASK_ID;
}
// update the map of instance counts
mFilterState.updateInstanceCountMap(taskGroups);
// Clear out desktop view if it is set
mDesktopTaskView = null;
- DesktopTask desktopTask = null;
// Add views as children based on whether it's grouped or single task. Looping through
// taskGroups backwards populates the thumbnail grid from least recent to most recent.
for (int i = taskGroups.size() - 1; i >= 0; i--) {
GroupTask groupTask = taskGroups.get(i);
- boolean isRemovalNeeded = stagedTaskIdToBeRemovedFromGrid != INVALID_TASK_ID
- && groupTask.containsTask(stagedTaskIdToBeRemovedFromGrid);
+ boolean isRemovalNeeded = stagedTaskIdToBeRemoved != INVALID_TASK_ID
+ && groupTask.containsTask(stagedTaskIdToBeRemoved);
- if (groupTask instanceof DesktopTask) {
- desktopTask = (DesktopTask) groupTask;
- // Desktop task will be added separately in the end
+ if (isRemovalNeeded && !groupTask.hasMultipleTasks()) {
+ // If the task we need to remove is not part of a pair, avoiding creating the
+ // TaskView.
continue;
}
- TaskView taskView;
- if (isRemovalNeeded && groupTask.hasMultipleTasks()) {
- // If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
- // to be a temporary container for the remaining task.
- taskView = getTaskViewFromPool(TaskView.Type.SINGLE);
- } else {
- taskView = getTaskViewFromPool(groupTask.taskViewType);
- }
-
- addView(taskView);
-
- if (isRemovalNeeded && groupTask.hasMultipleTasks()) {
- if (groupTask.task1.key.id == stagedTaskIdToBeRemovedFromGrid) {
- taskView.bind(groupTask.task2, mOrientationState);
- } else {
- taskView.bind(groupTask.task1, mOrientationState);
- }
- } else if (isRemovalNeeded) {
- // If the task we need to remove is not part of a pair, bind it to the TaskView
- // first (to prevent problems), then remove the whole thing.
- taskView.bind(groupTask.task1, mOrientationState);
- removeView(taskView);
- } else if (taskView instanceof GroupedTaskView) {
+ // If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
+ // to be a temporary container for the remaining task.
+ TaskView taskView = getTaskViewFromPool(
+ isRemovalNeeded ? TaskView.Type.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,
- groupTask.mSplitBounds);
+ mTaskOverlayFactory, groupTask.mSplitBounds);
+ } else if (taskView instanceof DesktopTaskView) {
+ ((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks,
+ mOrientationState, mTaskOverlayFactory);
+ mDesktopTaskView = (DesktopTaskView) taskView;
} else {
- taskView.bind(groupTask.task1, mOrientationState);
+ Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
+ : groupTask.task1;
+ taskView.bind(task, mOrientationState, mTaskOverlayFactory);
}
+ addView(taskView);
// enables instance filtering if the feature flag for it is on
if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) {
@@ -1802,32 +1846,13 @@
if (!taskGroups.isEmpty()) {
addView(mClearAllButton);
- if (isDesktopModeSupported()) {
- // Check if we have apps on the desktop
- if (desktopTask != null && !desktopTask.tasks.isEmpty()) {
- // If we are actively choosing apps for split, skip the desktop tile
- if (!getSplitSelectController().isSplitSelectActive()) {
- mDesktopTaskView = (DesktopTaskView) getTaskViewFromPool(
- TaskView.Type.DESKTOP);
- // Always add a desktop task to the first position
- addView(mDesktopTaskView, 0);
- mDesktopTaskView.bind(desktopTask.tasks, mOrientationState);
- }
- }
- }
}
// Keep same previous focused task
- TaskView newFocusedTaskView = getTaskViewByTaskIds(focusedTaskId);
+ TaskView newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds);
// If the list changed, maybe the focused task doesn't exist anymore
if (newFocusedTaskView == null && getTaskViewCount() > 0) {
newFocusedTaskView = getTaskViewAt(0);
- // Check if the first task is the desktop.
- // If first task is desktop, try to find another task to set as the focused task
- if (newFocusedTaskView != null && newFocusedTaskView.isDesktopTask()
- && getTaskViewCount() > 1) {
- newFocusedTaskView = getTaskViewAt(1);
- }
}
mFocusedTaskViewId = newFocusedTaskView != null && !enableGridOnlyOverview()
? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID;
@@ -1835,12 +1860,12 @@
updateChildTaskOrientations();
TaskView newRunningTaskView = null;
- if (hasAnyValidTaskIds(runningTaskId)) {
+ if (hasAllValidTaskIds(runningTaskIds)) {
// Update mRunningTaskViewId to be the new TaskView that was assigned by binding
// the full list of tasks to taskViews
- newRunningTaskView = getTaskViewByTaskIds(runningTaskId);
+ newRunningTaskView = getTaskViewByTaskIds(runningTaskIds);
if (newRunningTaskView != null) {
- mRunningTaskViewId = newRunningTaskView.getTaskViewId();
+ setRunningTaskViewId(newRunningTaskView.getTaskViewId());
} else {
if (mActiveGestureRunningTasks != null) {
// This will update mRunningTaskViewId and create a stub view if necessary.
@@ -1849,7 +1874,7 @@
// the current running task is excludedFromRecents.)
showCurrentTask(mActiveGestureRunningTasks);
} else {
- mRunningTaskViewId = INVALID_TASK_ID;
+ setRunningTaskViewId(INVALID_TASK_ID);
}
}
}
@@ -1858,8 +1883,8 @@
if (mNextPage != INVALID_PAGE) {
// Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll.
mCurrentPage = previousCurrentPage;
- if (hasAnyValidTaskIds(currentTaskId)) {
- currentTaskView = getTaskViewByTaskIds(currentTaskId);
+ if (hasAllValidTaskIds(currentTaskIds)) {
+ currentTaskView = getTaskViewByTaskIds(currentTaskIds);
if (currentTaskView != null) {
targetPage = indexOfChild(currentTaskView);
}
@@ -1868,15 +1893,10 @@
targetPage = previousFocusedPage;
} else {
// Set the current page to the running task, but not if settling on new task.
- if (hasAnyValidTaskIds(runningTaskId)) {
+ if (hasAllValidTaskIds(runningTaskIds)) {
targetPage = indexOfChild(newRunningTaskView);
} else if (getTaskViewCount() > 0) {
- TaskView taskView = requireTaskViewAt(0);
- // If first task id desktop, try to find another task to set the target page
- if (taskView.isDesktopTask() && getTaskViewCount() > 1) {
- taskView = requireTaskViewAt(1);
- }
- targetPage = indexOfChild(taskView);
+ targetPage = indexOfChild(requireTaskViewAt(0));
}
}
if (targetPage != -1 && mCurrentPage != targetPage) {
@@ -1973,7 +1993,8 @@
public void resetTaskVisuals() {
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
TaskView taskView = requireTaskViewAt(i);
- if (mIgnoreResetTaskId != taskView.getTaskIds()[0]) {
+ if (Arrays.stream(taskView.getTaskIds()).noneMatch(
+ taskId -> taskId == mIgnoreResetTaskId)) {
taskView.resetViewTransforms();
taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
taskView.setStableAlpha(mContentAlpha);
@@ -2010,6 +2031,9 @@
public void setFullscreenProgress(float fullscreenProgress) {
mFullscreenProgress = fullscreenProgress;
+ if (enableRefactorTaskThumbnail()) {
+ mRecentsViewData.getFullscreenProgress().setValue(mFullscreenProgress);
+ }
int taskCount = getTaskViewCount();
for (int i = 0; i < taskCount; i++) {
requireTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
@@ -2017,7 +2041,7 @@
mClearAllButton.setFullscreenProgress(fullscreenProgress);
// Fade out the actions view quickly (0.1 range)
- mActionsView.getFullscreenAlpha().setValue(
+ mActionsView.getFullscreenAlpha().updateValue(
mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR));
}
@@ -2037,9 +2061,9 @@
mInsets.set(insets);
// Update DeviceProfile dependant state.
- DeviceProfile dp = mActivity.getDeviceProfile();
+ DeviceProfile dp = mContainer.getDeviceProfile();
setOverviewGridEnabled(
- mActivity.getStateManager().getState().displayOverviewTasksAsGrid(dp));
+ getStateManager().getState().displayOverviewTasksAsGrid(dp));
if (enableGridOnlyOverview()) {
mActionsView.updateHiddenFlags(HIDDEN_ACTIONS_IN_MENU, dp.isTablet);
}
@@ -2073,12 +2097,17 @@
: View.LAYOUT_DIRECTION_RTL);
mClearAllButton.setRotation(getPagedOrientationHandler().getDegreesRotated());
- if (forceRecreateDragLayerControllers
- || !getPagedOrientationHandler().equals(oldOrientationHandler)) {
+ boolean isOrientationHandlerChanged =
+ !getPagedOrientationHandler().equals(oldOrientationHandler);
+ if (forceRecreateDragLayerControllers || isOrientationHandlerChanged) {
// Changed orientations, update controllers so they intercept accordingly.
- mActivity.getDragLayer().recreateControllers();
+ mContainer.getDragLayer().recreateControllers();
onOrientationChanged();
resetTaskVisuals();
+ // Log fake orientation changed.
+ if (isOrientationHandlerChanged) {
+ logOrientationChanged();
+ }
}
boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
@@ -2108,24 +2137,21 @@
// Update task size and padding that are dependent on DeviceProfile and insets.
private void updateSizeAndPadding() {
- DeviceProfile dp = mActivity.getDeviceProfile();
+ DeviceProfile dp = mContainer.getDeviceProfile();
getTaskSize(mLastComputedTaskSize);
mTaskWidth = mLastComputedTaskSize.width();
mTaskHeight = mLastComputedTaskSize.height();
-
setPadding(mLastComputedTaskSize.left - mInsets.left,
mLastComputedTaskSize.top - dp.overviewTaskThumbnailTopMarginPx - mInsets.top,
dp.widthPx - mInsets.right - mLastComputedTaskSize.right,
dp.heightPx - mInsets.bottom - mLastComputedTaskSize.bottom);
- mSizeStrategy.calculateGridSize(dp, mActivity, mLastComputedGridSize);
- mSizeStrategy.calculateGridTaskSize(mActivity, dp, mLastComputedGridTaskSize,
+ mSizeStrategy.calculateGridSize(dp, mContainer, mLastComputedGridSize);
+ mSizeStrategy.calculateGridTaskSize(mContainer, dp, mLastComputedGridTaskSize,
getPagedOrientationHandler());
- if (isDesktopModeSupported()) {
- mSizeStrategy.calculateDesktopTaskSize(mActivity, dp, mLastComputedDesktopTaskSize);
- }
+
if (enableGridOnlyOverview()) {
- mSizeStrategy.calculateCarouselTaskSize(mActivity, dp, mLastComputedCarouselTaskSize,
+ mSizeStrategy.calculateCarouselTaskSize(mContainer, dp, mLastComputedCarouselTaskSize,
getPagedOrientationHandler());
}
@@ -2159,14 +2185,15 @@
float accumulatedTranslationX = 0;
float translateXToMiddle = 0;
- if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet) {
+ if (enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet) {
translateXToMiddle = mIsRtl
? mLastComputedCarouselTaskSize.right - mLastComputedTaskSize.right
: mLastComputedCarouselTaskSize.left - mLastComputedTaskSize.left;
}
for (int i = 0; i < taskCount; i++) {
TaskView taskView = requireTaskViewAt(i);
- taskView.updateTaskSize();
+ taskView.updateTaskSize(mLastComputedTaskSize, mLastComputedGridTaskSize,
+ mLastComputedCarouselTaskSize);
taskView.setNonGridTranslationX(accumulatedTranslationX);
taskView.setNonGridPivotTranslationX(translateXToMiddle);
// Compensate space caused by TaskView scaling.
@@ -2181,7 +2208,7 @@
}
public void getTaskSize(Rect outRect) {
- mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
+ mSizeStrategy.calculateTaskSize(mContainer, mContainer.getDeviceProfile(), outRect,
getPagedOrientationHandler());
}
@@ -2224,18 +2251,13 @@
return mLastComputedGridTaskSize;
}
- /** Gets the last computed desktop task size */
- public Rect getLastComputedDesktopTaskSize() {
- return mLastComputedDesktopTaskSize;
- }
-
public Rect getLastComputedCarouselTaskSize() {
return mLastComputedCarouselTaskSize;
}
/** Gets the task size for modal state. */
public void getModalTaskSize(Rect outRect) {
- mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
+ mSizeStrategy.calculateModalTaskSize(mContainer, mContainer.getDeviceProfile(), outRect,
getPagedOrientationHandler());
}
@@ -2276,8 +2298,8 @@
}
private void animateActionsViewAlpha(float alphaValue, long duration) {
- mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(
- mActionsView.getVisibilityAlpha(), MULTI_PROPERTY_VALUE, alphaValue);
+ mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(mActionsView.getVisibilityAlpha(),
+ AnimatedFloat.VALUE, alphaValue);
mActionsViewAlphaAnimatorFinalValue = alphaValue;
mActionsViewAlphaAnimator.setDuration(duration);
// Set autocancel to prevent race-conditiony setting of alpha from other animations
@@ -2296,12 +2318,12 @@
mClearAllButton.onRecentsViewScroll(scroll, mOverviewGridEnabled);
// Clear all button alpha was set by the previous line.
- mActionsView.getIndexScrollAlpha().setValue(1 - mClearAllButton.getScrollAlpha());
+ mActionsView.getIndexScrollAlpha().updateValue(1 - mClearAllButton.getScrollAlpha());
}
@Override
protected int getDestinationPage(int scaledScroll) {
- if (!mActivity.getDeviceProfile().isTablet) {
+ if (!mContainer.getDeviceProfile().isTablet) {
return super.getDestinationPage(scaledScroll);
}
if (!isPageScrollsInitialized()) {
@@ -2357,11 +2379,13 @@
upper = Math.min(centerPageIndex + 2, numChildren - 1);
}
+ List<Integer> visibleTaskIds = new ArrayList<>();
+
// Update the task data for the in/visible children
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView taskView = requireTaskViewAt(i);
- TaskIdAttributeContainer[] containers = taskView.getTaskIdAttributeContainers();
- if (containers[0] == null && containers[1] == null) {
+ List<TaskContainer> containers = taskView.getTaskContainers();
+ if (containers.isEmpty()) {
continue;
}
int index = indexOfChild(taskView);
@@ -2373,9 +2397,13 @@
}
if (visible) {
// Default update all non-null tasks, then remove running ones
- List<Task> tasksToUpdate = Arrays.stream(containers).filter(Objects::nonNull)
- .map(TaskIdAttributeContainer::getTask)
+ List<Task> tasksToUpdate = containers.stream()
+ .map(TaskContainer::getTask)
.collect(Collectors.toCollection(ArrayList::new));
+ if (enableRefactorTaskThumbnail()) {
+ visibleTaskIds.addAll(
+ tasksToUpdate.stream().map((task) -> task.key.id).toList());
+ }
if (mTmpRunningTasks != null) {
for (Task t : mTmpRunningTasks) {
// Skip loading if this is the task that we are animating into
@@ -2399,7 +2427,7 @@
mHasVisibleTaskData.put(task.key.id, visible);
}
} else {
- for (TaskIdAttributeContainer container : containers) {
+ for (TaskContainer container : containers) {
if (container == null) {
continue;
}
@@ -2411,6 +2439,9 @@
}
}
}
+ if (enableRefactorTaskThumbnail()) {
+ mTasksRepository.setVisibleTasks(visibleTaskIds);
+ }
}
/**
@@ -2451,7 +2482,7 @@
}
public void startHome() {
- startHome(mActivity.isStarted());
+ startHome(mContainer.isStarted());
}
public void startHome(boolean animated) {
@@ -2464,6 +2495,10 @@
/** Returns whether user can start home based on state in {@link OverviewCommandHelper}. */
protected abstract boolean canStartHomeSafely();
+ /** Returns the state manager used in RecentsView **/
+ public abstract StateManager<STATE_TYPE,
+ ? extends StatefulContainer<STATE_TYPE>> getStateManager();
+
public void reset() {
setCurrentTask(-1);
mCurrentPageScrollDiff = 0;
@@ -2514,7 +2549,7 @@
// For now 2 distinct task IDs is max for split screen
TaskView runningTaskView = getTaskViewFromTaskViewId(taskViewId);
if (runningTaskView == null) {
- return INVALID_TASK_IDS;
+ return new int[0];
}
return runningTaskView.getTaskIds();
@@ -2595,6 +2630,9 @@
if (!mModel.isTaskListValid(mTaskListChangeId)) {
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan, RecentsFilterState
.getFilter(mFilterState.getPackageNameToFilter()));
+ if (enableRefactorTaskThumbnail()) {
+ mTasksRepository.getAllTaskData(/* forceRefresh = */ true);
+ }
}
}
@@ -2603,6 +2641,7 @@
*/
public void onGestureAnimationStart(
Task[] runningTasks, RotationTouchHelper rotationTouchHelper) {
+ Log.d(TAG, "onGestureAnimationStart - runningTasks: " + Arrays.toString(runningTasks));
mActiveGestureRunningTasks = runningTasks;
// This needs to be called before the other states are set since it can create the task view
if (mOrientationState.setGestureActive(true)) {
@@ -2638,26 +2677,33 @@
// Let system take care of the rotation
return;
}
+
+ if (mRunningTaskShowScreenshot) {
+ animateRotation(newRotation);
+ } else {
+ // Animate the rotation and stops running task
+ switchToScreenshot(() -> {
+ animateRotation(newRotation);
+ finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
+ null /* onFinishComplete */);
+ });
+ }
+ }
+
+ private void animateRotation(int newRotation) {
+ AbstractFloatingView.closeAllOpenViewsExcept(mContainer, false, TYPE_REBIND_SAFE);
AnimatorSet pa = setRecentsChangedOrientation(true);
pa.addListener(AnimatorListeners.forSuccessCallback(() -> {
setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
- mActivity.getDragLayer().recreateControllers();
+ mContainer.getDragLayer().recreateControllers();
setRecentsChangedOrientation(false).start();
}));
pa.start();
}
- public AnimatorSet setRecentsChangedOrientation(boolean fadeInChildren) {
- getRunningTaskIndex();
- int runningIndex = getCurrentPage();
+ public AnimatorSet setRecentsChangedOrientation(boolean fadeOut) {
AnimatorSet as = new AnimatorSet();
- for (int i = 0; i < getTaskViewCount(); i++) {
- View taskView = requireTaskViewAt(i);
- if (runningIndex == i && taskView.getAlpha() != 0) {
- continue;
- }
- as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1));
- }
+ as.play(ObjectAnimator.ofFloat(this, View.ALPHA, fadeOut ? 0 : 1));
return as;
}
@@ -2670,7 +2716,7 @@
if (!shouldRotateMenuForFakeRotation) {
return;
}
- TaskMenuView tv = (TaskMenuView) getTopOpenViewWithType(mActivity, TYPE_TASK_MENU);
+ TaskMenuView tv = (TaskMenuView) getTopOpenViewWithType(mContainer, TYPE_TASK_MENU);
if (tv != null) {
// Rotation is supported on phone (details at b/254198019#comment4)
tv.onRotationChanged();
@@ -2691,7 +2737,7 @@
}
BaseState<?> endState = mSizeStrategy.stateFromGestureEndTarget(endTarget);
- if (endState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())) {
+ if (endState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) {
TaskView runningTaskView = getRunningTaskView();
float runningTaskPrimaryGridTranslation = 0;
float runningTaskSecondaryGridTranslation = 0;
@@ -2751,18 +2797,19 @@
* Returns true if we should add a stub taskView for the running task id
*/
protected boolean shouldAddStubTaskView(Task[] runningTasks) {
- if (runningTasks.length > 1) {
- TaskView primaryTaskView = getTaskViewByTaskId(runningTasks[0].key.id);
- TaskView secondaryTaskView = getTaskViewByTaskId(runningTasks[1].key.id);
- int leftTopTaskViewId =
- (primaryTaskView == null) ? -1 : primaryTaskView.getTaskViewId();
- int rightBottomTaskViewId =
- (secondaryTaskView == null) ? -1 : secondaryTaskView.getTaskViewId();
- // Add a new stub view if both taskIds don't match any taskViews
- return leftTopTaskViewId != rightBottomTaskViewId || leftTopTaskViewId == -1;
+ int[] runningTaskIds = Arrays.stream(runningTasks).mapToInt(task -> task.key.id).toArray();
+ TaskView matchingTaskView = null;
+ if (hasDesktopTask(runningTasks) && runningTaskIds.length == 1) {
+ // TODO(b/249371338): Unsure if it's expected, desktop runningTasks only have a single
+ // taskId, therefore we match any DesktopTaskView that contains the runningTaskId.
+ TaskView taskview = getTaskViewByTaskId(runningTaskIds[0]);
+ if (taskview instanceof DesktopTaskView) {
+ matchingTaskView = taskview;
+ }
+ } else {
+ matchingTaskView = getTaskViewByTaskIds(runningTaskIds);
}
- Task runningTaskInfo = runningTasks[0];
- return runningTaskInfo != null && getTaskViewByTaskId(runningTaskInfo.key.id) == null;
+ return matchingTaskView == null;
}
/**
@@ -2772,6 +2819,7 @@
* is called. Also scrolls the view to this task.
*/
private void showCurrentTask(Task[] runningTasks) {
+ Log.d(TAG, "showCurrentTask - runningTasks: " + Arrays.toString(runningTasks));
if (runningTasks.length == 0) {
return;
}
@@ -2785,26 +2833,24 @@
if (needDesktopTask) {
taskView = getTaskViewFromPool(TaskView.Type.DESKTOP);
mTmpRunningTasks = Arrays.copyOf(runningTasks, runningTasks.length);
- addView(taskView, 0);
((DesktopTaskView) taskView).bind(Arrays.asList(mTmpRunningTasks),
- mOrientationState);
+ mOrientationState, mTaskOverlayFactory);
} else if (needGroupTaskView) {
taskView = getTaskViewFromPool(TaskView.Type.GROUPED);
mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]};
- addView(taskView, 0);
// When we create a placeholder task view mSplitBoundsConfig will be null, but with
// the actual app running we won't need to show the thumbnail until all the tasks
// load later anyways
((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
- mOrientationState, mSplitBoundsConfig);
+ mOrientationState, mTaskOverlayFactory, mSplitBoundsConfig);
} else {
taskView = getTaskViewFromPool(TaskView.Type.SINGLE);
- addView(taskView, 0);
// The temporary running task is only used for the duration between the start of the
// gesture and the task list is loaded and applied
mTmpRunningTasks = new Task[]{runningTasks[0]};
- taskView.bind(mTmpRunningTasks[0], mOrientationState);
+ taskView.bind(mTmpRunningTasks[0], mOrientationState, mTaskOverlayFactory);
}
+ addView(taskView, 0);
runningTaskViewId = taskView.getTaskViewId();
if (wasEmpty) {
addView(mClearAllButton);
@@ -2834,7 +2880,7 @@
}
private boolean hasDesktopTask(Task[] runningTasks) {
- if (!isDesktopModeSupported()) {
+ if (!DesktopModeStatus.canEnterDesktopMode(mContext)) {
return false;
}
for (Task task : runningTasks) {
@@ -2859,7 +2905,23 @@
setRunningTaskViewShowScreenshot(true);
setRunningTaskHidden(false);
}
+ setRunningTaskViewId(runningTaskViewId);
+ }
+
+ private void setRunningTaskViewId(int runningTaskViewId) {
+ int prevRunningTaskViewId = mRunningTaskViewId;
mRunningTaskViewId = runningTaskViewId;
+
+ if (Flags.enableRefactorTaskThumbnail()) {
+ TaskView previousRunningTaskView = getTaskViewFromTaskViewId(prevRunningTaskViewId);
+ if (previousRunningTaskView != null) {
+ previousRunningTaskView.notifyIsRunningTaskUpdated();
+ }
+ TaskView newRunningTaskView = getTaskViewFromTaskViewId(runningTaskViewId);
+ if (newRunningTaskView != null) {
+ newRunningTaskView.notifyIsRunningTaskUpdated();
+ }
+ }
}
private int getTaskViewIdFromTaskId(int taskId) {
@@ -2886,7 +2948,7 @@
mRunningTaskShowScreenshot = showScreenshot;
TaskView runningTaskView = getRunningTaskView();
if (runningTaskView != null) {
- runningTaskView.setShowScreenshot(mRunningTaskShowScreenshot);
+ runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot);
}
}
@@ -2911,7 +2973,6 @@
int taskCount = getTaskViewCount();
for (int i = 0; i < taskCount; i++) {
TaskView taskView = requireTaskViewAt(i);
- taskView.setIconScaleAnimStartProgress(0f);
taskView.animateIconScaleAndDimIntoView();
}
}
@@ -2954,7 +3015,7 @@
return;
}
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ DeviceProfile deviceProfile = mContainer.getDeviceProfile();
int taskTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
int topRowWidth = 0;
@@ -2979,8 +3040,6 @@
TaskView homeTaskView = getHomeTaskView();
TaskView nextFocusedTaskView = null;
- int desktopTaskIndex = Integer.MAX_VALUE;
-
if (!isTaskDismissal) {
mTopRowIdSet.clear();
}
@@ -3007,21 +3066,6 @@
// If focused task is snapped, the row width is just task width and spacing.
snappedTaskRowWidth = taskWidthAndSpacing;
}
- } else if (taskView.isDesktopTask()) {
- // Desktop task was not focused. Pin it to the right of focused
- desktopTaskIndex = i;
- if (taskView.getVisibility() == View.GONE) {
- // Desktop task view is hidden, skip it from grid calculations
- continue;
- }
- if (!enableGridOnlyOverview()) {
- // Only apply x-translation when using legacy overview grid
- gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
- }
-
- // Center view vertically in case it's from different orientation.
- taskView.setGridTranslationY((mLastComputedDesktopTaskSize.height() + taskTopMargin
- - taskView.getLayoutParams().height) / 2f);
} else {
if (i > focusedTaskIndex) {
// For tasks after the focused task, shift by focused task's width and spacing.
@@ -3062,7 +3106,7 @@
// Move horizontally into empty space.
float widthOffset = 0;
for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) {
- if (j == focusedTaskIndex || j == desktopTaskIndex) {
+ if (j == focusedTaskIndex) {
continue;
}
widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
@@ -3081,7 +3125,7 @@
// Move horizontally into empty space.
float widthOffset = 0;
for (int j = i - 1; !bottomSet.contains(j) && j >= 0; j--) {
- if (j == focusedTaskIndex || j == desktopTaskIndex) {
+ if (j == focusedTaskIndex) {
continue;
}
widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
@@ -3295,7 +3339,7 @@
int secondaryTaskDimension = getPagedOrientationHandler().getSecondaryDimension(taskView);
int verticalFactor = getPagedOrientationHandler().getSecondaryTranslationDirectionFactor();
- ResourceProvider rp = DynamicResource.provider(mActivity);
+ ResourceProvider rp = DynamicResource.provider(mContainer);
SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
.setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
.setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness));
@@ -3323,10 +3367,10 @@
*/
private void createInitialSplitSelectAnimation(PendingAnimation anim) {
getPagedOrientationHandler().getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
- mSplitPlaceholderInset, mActivity.getDeviceProfile(),
+ mSplitPlaceholderInset, mContainer.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
SplitAnimationTimings timings =
- AnimUtils.getDeviceOverviewToSplitTimings(mActivity.getDeviceProfile().isTablet);
+ AnimUtils.getDeviceOverviewToSplitTimings(mContainer.getDeviceProfile().isTablet);
RectF startingTaskRect = new RectF();
safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
@@ -3342,7 +3386,7 @@
timings.getIconFadeEndOffset()));
}
- FloatingTaskView firstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+ FloatingTaskView firstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mContainer,
splitAnimInitProps.getOriginalView(),
splitAnimInitProps.getOriginalBitmap(),
splitAnimInitProps.getIconDrawable(), startingTaskRect);
@@ -3354,13 +3398,13 @@
// Allow user to click staged app to launch into fullscreen
firstFloatingTaskView.setOnClickListener(view ->
mSplitSelectStateController.getSplitAnimationController().
- playAnimPlaceholderToFullscreen(mActivity, view,
+ playAnimPlaceholderToFullscreen(mContainer, view,
Optional.of(() -> resetFromSplitSelectionState())));
// SplitInstructionsView: animate in
safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
SplitInstructionsView splitInstructionsView =
- SplitInstructionsView.getSplitInstructionsView(mActivity);
+ SplitInstructionsView.getSplitInstructionsView(mContainer);
splitInstructionsView.setAlpha(0);
anim.setViewAlpha(splitInstructionsView, 1, clampToProgress(LINEAR,
timings.getInstructionsContainerFadeInStartOffset(),
@@ -3486,7 +3530,7 @@
boolean isClearAllHidden = isClearAllHidden();
boolean snapToLastTask = false;
boolean isLeftRightSplit =
- mActivity.getDeviceProfile().isLeftRightSplit && isSplitSelectionActive();
+ mContainer.getDeviceProfile().isLeftRightSplit && isSplitSelectionActive();
TaskView lastGridTaskView = showAsGrid ? getLastGridTaskView() : null;
int currentPageScroll = getScrollForPage(mCurrentPage);
int lastGridTaskScroll = getScrollForPage(indexOfChild(lastGridTaskView));
@@ -3531,7 +3575,7 @@
// and adjust accordingly to the new shortTotalCompensation after dismiss.
int newClearAllShortTotalWidthTranslation = 0;
if (longRowWidth < mLastComputedGridSize.width()) {
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ DeviceProfile deviceProfile = mContainer.getDeviceProfile();
newClearAllShortTotalWidthTranslation =
(mIsRtl
? mLastComputedTaskSize.right
@@ -3618,7 +3662,7 @@
}
SplitAnimationTimings splitTimings =
- AnimUtils.getDeviceOverviewToSplitTimings(mActivity.getDeviceProfile().isTablet);
+ AnimUtils.getDeviceOverviewToSplitTimings(mContainer.getDeviceProfile().isTablet);
int distanceFromDismissedTask = 0;
for (int i = 0; i < count; i++) {
@@ -3747,7 +3791,7 @@
if (taskView == nextFocusedTaskView) {
// Enlarge the task to be focused next, and translate into focus position.
float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
- anim.setFloat(taskView, TaskView.SNAPSHOT_SCALE, scale,
+ anim.setFloat(taskView, TaskView.DISMISS_SCALE, scale,
clampToProgress(LINEAR, animationStartProgress,
dismissTranslationInterpolationEnd));
anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
@@ -3761,7 +3805,7 @@
anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(),
secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
dismissTranslationInterpolationEnd));
- anim.setFloat(taskView, TaskView.FOCUS_TRANSITION, 0f,
+ anim.add(taskView.getFocusTransitionScaleAndDimOutAnimator(),
clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
} else {
float primaryTranslation =
@@ -3818,19 +3862,17 @@
if (success) {
if (shouldRemoveTask) {
- if (dismissedTaskView.getTask() != null) {
- if (dismissedTaskView.isRunningTask()) {
- finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
- () -> removeTaskInternal(dismissedTaskViewId));
- } else {
- removeTaskInternal(dismissedTaskViewId);
- }
- announceForAccessibility(
- getResources().getString(R.string.task_view_closed));
- mActivity.getStatsLogManager().logger()
- .withItemInfo(dismissedTaskView.getItemInfo())
- .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
+ if (dismissedTaskView.isRunningTask()) {
+ finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
+ () -> removeTaskInternal(dismissedTaskViewId));
+ } else {
+ removeTaskInternal(dismissedTaskViewId);
}
+ announceForAccessibility(
+ getResources().getString(R.string.task_view_closed));
+ mContainer.getStatsLogManager().logger()
+ .withItemInfo(dismissedTaskView.getFirstItemInfo())
+ .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
}
int pageToSnapTo = mCurrentPage;
@@ -3879,7 +3921,8 @@
taskViewIdArray.removeValue(
finalNextFocusedTaskView.getTaskViewId());
}
- if (snappedIndex < taskViewIdArray.size()) {
+ if (snappedIndex >= 0
+ && snappedIndex < taskViewIdArray.size()) {
taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex);
} else if (snappedIndex == taskViewIdArray.size()) {
// If the snapped task is the last item from the
@@ -3993,7 +4036,7 @@
// Update various scroll-dependent UI.
dispatchScrollChanged();
updateActionsViewFocusedScroll();
- if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
+ if (isClearAllHidden() && !mContainer.getDeviceProfile().isTablet) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING,
false);
}
@@ -4007,22 +4050,33 @@
}
/**
- * Hides all overview actions if current page is for split apps, shows otherwise
- * If actions are showing, we only show split option if
+ * Hides all overview actions if user is halfway through split selection, shows otherwise.
+ * We only show split option if:
+ * * Focused view is a single app
* * Device is large screen
- * * There are at least 2 tasks to invoke split
*/
private void updateCurrentTaskActionsVisibility() {
- boolean isCurrentSplit = getCurrentPageTaskView() instanceof GroupedTaskView;
- mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
- mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
- mActionsView.updateSplitButtonHiddenFlags(FLAG_IS_NOT_TABLET,
- !mActivity.getDeviceProfile().isTablet);
- mActionsView.updateSplitButtonDisabledFlags(FLAG_SINGLE_TASK, /*enable=*/ false);
- if (isDesktopModeSupported()) {
- boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
- mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
+ TaskView taskView = getCurrentPageTaskView();
+ boolean isCurrentSplit = taskView instanceof GroupedTaskView;
+ GroupedTaskView groupedTaskView = isCurrentSplit ? (GroupedTaskView) taskView : null;
+ // Update flags to see if entire actions bar should be hidden.
+ if (!FeatureFlags.enableAppPairs()) {
+ mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
}
+ mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
+ // Update flags to see if actions bar should show buttons for a single task or a pair of
+ // tasks.
+ boolean canSaveAppPair = isCurrentSplit && supportsAppPairs() &&
+ getSplitSelectController().getAppPairsController().canSaveAppPair(groupedTaskView);
+ mActionsView.updateForGroupedTask(isCurrentSplit, canSaveAppPair);
+
+ boolean isCurrentDesktop = taskView instanceof DesktopTaskView;
+ mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
+ }
+
+ /** Returns if app pairs are supported in this launcher. Overridden in subclasses. */
+ public boolean supportsAppPairs() {
+ return true;
}
/**
@@ -4208,7 +4262,7 @@
@SuppressWarnings("unused")
private void dismissAllTasks(View view) {
runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
- mActivity.getStatsLogManager().logger().log(LAUNCHER_TASK_CLEAR_ALL);
+ mContainer.getStatsLogManager().logger().log(LAUNCHER_TASK_CLEAR_ALL);
}
private void dismissCurrentTask() {
@@ -4278,13 +4332,10 @@
alpha = Utilities.boundToRange(alpha, 0, 1);
mContentAlpha = alpha;
- int runningTaskId = getTaskIdsForRunningTaskView()[0];
+ TaskView runningTaskView = getRunningTaskView();
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
TaskView child = requireTaskViewAt(i);
- int[] childTaskIds = child.getTaskIds();
- if (runningTaskId != INVALID_TASK_ID
- && mRunningTaskTileHidden
- && (childTaskIds[0] == runningTaskId || childTaskIds[1] == runningTaskId)) {
+ if (runningTaskView != null && mRunningTaskTileHidden && child == runningTaskView) {
continue;
}
child.setStableAlpha(alpha);
@@ -4293,7 +4344,7 @@
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
mEmptyIcon.setAlpha(alphaInt);
- mActionsView.getContentAlpha().setValue(mContentAlpha);
+ mActionsView.getContentAlpha().updateValue(mContentAlpha);
if (alpha > 0) {
setVisibility(VISIBLE);
@@ -4337,8 +4388,12 @@
* Updates {@link RecentsOrientedState}'s cached RecentsView rotation.
*/
public void updateRecentsRotation() {
- final int rotation = mActivity.getDisplay().getRotation();
- mOrientationState.setRecentsRotation(rotation);
+ final int rotation = TraceHelper.allowIpcs(
+ "RecentsView.updateRecentsRotation", () -> mContainer.getDisplay().getRotation());
+ // Log real orientation change.
+ if (mOrientationState.setRecentsRotation(rotation)) {
+ logOrientationChanged();
+ }
}
public void setLayoutRotation(int touchRotation, int displayRotation) {
@@ -4451,28 +4506,42 @@
} else {
// Only update pivot when it is tablet and not in grid yet, so the pivot is correct
// for non-current tasks when swiping up to overview
- if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
+ if (enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet
&& !mOverviewGridEnabled) {
mTempRect.set(mLastComputedCarouselTaskSize);
} else {
mTempRect.set(mLastComputedTaskSize);
}
getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
- mActivity.getDeviceProfile(), mTempPointF);
+ mContainer.getDeviceProfile(), mTempPointF);
}
setPivotX(mTempPointF.x);
setPivotY(mTempPointF.y);
}
+ /**
+ * Sets whether we should force-override the page offset mid-point to the current task, rather
+ * than the running task, when updating page offsets.
+ */
+ public void setOffsetMidpointIndexOverride(int offsetMidpointIndexOverride) {
+ if (!enableAdditionalHomeAnimations()) {
+ return;
+ }
+ mOffsetMidpointIndexOverride = offsetMidpointIndexOverride;
+ updatePageOffsets();
+ }
+
private void updatePageOffsets() {
float offset = mAdjacentPageHorizontalOffset;
float modalOffset = ACCELERATE_0_75.getInterpolation(mTaskModalness);
int count = getChildCount();
boolean showAsGrid = showAsGrid();
- TaskView runningTask = mRunningTaskViewId == -1 || !mRunningTaskTileHidden
+ TaskView runningTask = mRunningTaskViewId == INVALID_PAGE || !mRunningTaskTileHidden
? null : getRunningTaskView();
- int midpoint = runningTask == null ? -1 : indexOfChild(runningTask);
+ int midpoint = mOffsetMidpointIndexOverride == INVALID_PAGE
+ ? (runningTask == null ? INVALID_PAGE : indexOfChild(runningTask))
+ : mOffsetMidpointIndexOverride;
int modalMidpoint = getCurrentPage();
boolean isModalGridWithoutFocusedTask =
showAsGrid && enableGridOnlyOverview() && mTaskModalness > 0;
@@ -4558,7 +4627,7 @@
TaskView taskView = (TaskView) child;
outRect.offset(taskView.getPersistentTranslationX(),
taskView.getPersistentTranslationY());
- outRect.top += mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+ outRect.top += mContainer.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
mTempMatrix.reset();
float persistentScale = taskView.getPersistentScale();
@@ -4670,7 +4739,7 @@
if (isTopShift) {
distanceToOffscreen = -taskPosition.bottom;
} else if (isBottomShift) {
- distanceToOffscreen = mActivity.getDeviceProfile().heightPx - taskPosition.top;
+ distanceToOffscreen = mContainer.getDeviceProfile().heightPx - taskPosition.top;
}
return distanceToOffscreen * offsetProgress;
}
@@ -4716,7 +4785,8 @@
*/
public void resetModalVisuals() {
if (mSelectedTask != null) {
- mSelectedTask.getThumbnail().getTaskOverlay().resetModalVisuals();
+ mSelectedTask.taskContainers.forEach(
+ taskContainer -> taskContainer.getOverlay().resetModalVisuals());
}
}
@@ -4726,7 +4796,7 @@
*/
public void initiateSplitSelect(TaskView taskView) {
int defaultSplitPosition = getPagedOrientationHandler()
- .getDefaultSplitPosition(mActivity.getDeviceProfile());
+ .getDefaultSplitPosition(mContainer.getDeviceProfile());
initiateSplitSelect(taskView, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
}
@@ -4734,14 +4804,12 @@
public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
mSplitHiddenTaskView = taskView;
- mSplitSelectStateController.setInitialTaskSelect(null /*intent*/,
- stagePosition, taskView.getItemInfo(), splitEvent, taskView.mTask.key.id);
+ mSplitSelectStateController.setInitialTaskSelect(null /*intent*/, stagePosition,
+ taskView.getFirstItemInfo(), splitEvent, taskView.getFirstTask().key.id);
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
- if (isDesktopModeSupported()) {
- updateDesktopTaskVisibility(false /* visible */);
- }
+ updateDesktopTaskVisibility(false /* visible */);
}
/**
@@ -4759,13 +4827,11 @@
// Prevent dismissing whole task if we're only initiating from one of 2 tasks in split pair
mSplitSelectStateController.setDismissingFromSplitPair(mSplitHiddenTaskView != null
- && mSplitHiddenTaskView.containsMultipleTasks());
+ && mSplitHiddenTaskView instanceof GroupedTaskView);
mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
- if (isDesktopModeSupported()) {
- updateDesktopTaskVisibility(false /* visible */);
- }
+ updateDesktopTaskVisibility(false /* visible */);
}
private void updateDesktopTaskVisibility(boolean visible) {
@@ -4782,20 +4848,20 @@
mSplitSelectStateController.isAnimateCurrentTaskDismissal();
boolean isInitiatingTaskViewSplitPair =
mSplitSelectStateController.isDismissingFromSplitPair();
- if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair) {
+ if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair
+ && mSplitHiddenTaskView instanceof GroupedTaskView) {
// Splitting from Overview for split pair task
createInitialSplitSelectAnimation(builder);
// Animate pair thumbnail into full thumbnail
- boolean primaryTaskSelected =
- mSplitHiddenTaskView.getTaskIdAttributeContainers()[0].getTask().key.id ==
- mSplitSelectStateController.getInitialTaskId();
- TaskIdAttributeContainer taskIdAttributeContainer = mSplitHiddenTaskView
- .getTaskIdAttributeContainers()[primaryTaskSelected ? 1 : 0];
- TaskThumbnailView thumbnail = taskIdAttributeContainer.getThumbnailView();
+ boolean primaryTaskSelected = mSplitHiddenTaskView.getTaskIds()[0]
+ == mSplitSelectStateController.getInitialTaskId();
+ TaskContainer taskContainer = mSplitHiddenTaskView
+ .getTaskContainers().get(primaryTaskSelected ? 1 : 0);
+ TaskThumbnailViewDeprecated thumbnail = taskContainer.getThumbnailViewDeprecated();
mSplitSelectStateController.getSplitAnimationController()
- .addInitialSplitFromPair(taskIdAttributeContainer, builder,
- mActivity.getDeviceProfile(),
+ .addInitialSplitFromPair(taskContainer, builder,
+ mContainer.getDeviceProfile(),
mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
primaryTaskSelected);
builder.addOnFrameCallback(() ->{
@@ -4863,17 +4929,19 @@
Rect firstTaskStartingBounds = new Rect();
Rect firstTaskEndingBounds = mTempRect;
- boolean isTablet = mActivity.getDeviceProfile().isTablet;
+ boolean isTablet = mContainer.getDeviceProfile().isTablet;
SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration());
int halfDividerSize = getResources()
.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
getPagedOrientationHandler().getFinalSplitPlaceholderBounds(halfDividerSize,
- mActivity.getDeviceProfile(),
+ mContainer.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
secondTaskEndingBounds);
+ mSplitScrim = mSplitSelectStateController.getSplitAnimationController()
+ .addScrimBehindAnim(pendingAnimation, mContainer, getContext());
FloatingTaskView firstFloatingTaskView =
mSplitSelectStateController.getFirstFloatingTaskView();
firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
@@ -4883,7 +4951,7 @@
safeRemoveDragLayerView(mSecondFloatingTaskView);
- mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity, secondView,
+ mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mContainer, secondView,
thumbnail, drawable, secondTaskStartingBounds);
mSecondFloatingTaskView.setAlpha(1);
mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
@@ -4928,10 +4996,11 @@
safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
safeRemoveDragLayerView(mSecondFloatingTaskView);
safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
+ safeRemoveDragLayerView(mSplitScrim);
mSecondFloatingTaskView = null;
mSplitSelectSource = null;
mSplitSelectStateController.getSplitAnimationController()
- .removeSplitInstructionsView(mActivity);
+ .removeSplitInstructionsView(mContainer);
}
if (mSecondSplitHiddenView != null) {
@@ -4952,7 +5021,7 @@
if (mSplitHiddenTaskViewIndex == -1) {
return;
}
- if (!mActivity.getDeviceProfile().isTablet) {
+ if (!mContainer.getDeviceProfile().isTablet) {
int pageToSnapTo = mCurrentPage;
if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
pageToSnapTo += 1;
@@ -4969,14 +5038,12 @@
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
mSplitHiddenTaskView = null;
}
- if (isDesktopModeSupported()) {
- updateDesktopTaskVisibility(true /* visible */);
- }
+ updateDesktopTaskVisibility(true /* visible */);
}
private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
if (viewToRemove != null) {
- mActivity.getDragLayer().removeView(viewToRemove);
+ mContainer.getDragLayer().removeView(viewToRemove);
}
}
@@ -4985,11 +5052,11 @@
* Note that the translation can be its primary or secondary dimension.
*/
public float getSplitSelectTranslation() {
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ DeviceProfile deviceProfile = mContainer.getDeviceProfile();
RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler();
int splitPosition = getSplitSelectController().getActiveSplitStagePosition();
int splitPlaceholderSize =
- mActivity.getResources().getDimensionPixelSize(R.dimen.split_placeholder_size);
+ mContainer.getResources().getDimensionPixelSize(R.dimen.split_placeholder_size);
int direction = orientationHandler.getSplitTranslationDirectionFactor(
splitPosition, deviceProfile);
@@ -5012,7 +5079,7 @@
protected void onRotateInSplitSelectionState() {
getPagedOrientationHandler().getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
- mSplitPlaceholderInset, mActivity.getDeviceProfile(),
+ mSplitPlaceholderInset, mContainer.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
mTempRectF.set(mTempRect);
FloatingTaskView firstFloatingTaskView =
@@ -5021,10 +5088,10 @@
firstFloatingTaskView.update(mTempRectF, /*progress=*/1f);
RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler();
- Pair<FloatProperty, FloatProperty> taskViewsFloat =
+ Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat =
orientationHandler.getSplitSelectTaskOffset(
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
- mActivity.getDeviceProfile());
+ mContainer.getDeviceProfile());
taskViewsFloat.first.set(this, getSplitSelectTranslation());
taskViewsFloat.second.set(this, 0f);
@@ -5157,17 +5224,21 @@
* Returns the scale up required on the view, so that it coves the screen completely
*/
public float getMaxScaleForFullScreen() {
- if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
+ if (enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet
&& !mOverviewGridEnabled) {
+ if (mLastComputedCarouselTaskSize.isEmpty()) {
+ mSizeStrategy.calculateCarouselTaskSize(mContainer, mContainer.getDeviceProfile(),
+ mLastComputedCarouselTaskSize, getPagedOrientationHandler());
+ }
mTempRect.set(mLastComputedCarouselTaskSize);
} else {
- if (mLastComputedTaskSize.height() == 0 || mLastComputedTaskSize.width() == 0) {
+ if (mLastComputedTaskSize.isEmpty()) {
getTaskSize(mLastComputedTaskSize);
}
mTempRect.set(mLastComputedTaskSize);
}
return getPagedViewOrientedState().getFullScreenScaleAndPivot(
- mTempRect, mActivity.getDeviceProfile(), mTempPointF);
+ mTempRect, mContainer.getDeviceProfile(), mTempPointF);
}
public PendingAnimation createTaskLaunchAnimation(
@@ -5186,17 +5257,17 @@
updateGridProperties();
updateScrollSynchronously();
- int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
+ int targetSysUiFlags = tv.getFirstThumbnailViewDeprecated().getSysUiStatusNavFlags();
final boolean[] passedOverviewThreshold = new boolean[] {false};
ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
progressAnim.addUpdateListener(animator -> {
// Once we pass a certain threshold, update the sysui flags to match the target
// tasks' flags
if (animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD) {
- mActivity.getSystemUiController().updateUiState(
+ mContainer.getSystemUiController().updateUiState(
UI_STATE_FULLSCREEN_TASK, targetSysUiFlags);
} else {
- mActivity.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
+ mContainer.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
}
// Passing the threshold from taskview to fullscreen app will vibrate
@@ -5218,7 +5289,7 @@
DepthController depthController = getDepthController();
if (depthController != null) {
ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController.stateDepth,
- MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mActivity));
+ MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mContainer));
anim.play(depthAnimator);
}
anim.play(ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, 0f, 1f));
@@ -5234,7 +5305,8 @@
mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
mPendingAnimation.addEndListener(isSuccess -> {
if (isSuccess) {
- if (tv.getTaskIds()[1] != -1 && mRemoteTargetHandles != null) {
+ if (tv instanceof GroupedTaskView && hasAllValidTaskIds(tv.getTaskIds())
+ && mRemoteTargetHandles != null) {
// TODO(b/194414938): make this part of the animations instead.
TaskViewUtils.createSplitAuxiliarySurfacesAnimator(
mRemoteTargetHandles[0].getTransformParams().getTargetSet().nonApps,
@@ -5249,11 +5321,8 @@
} else {
tv.launchTask(this::onTaskLaunchAnimationEnd);
}
- Task task = tv.getTask();
- if (task != null) {
- mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
- .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
- }
+ mContainer.getStatsLogManager().logger().withItemInfo(tv.getFirstItemInfo())
+ .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
} else {
onTaskLaunchAnimationEnd(false);
}
@@ -5262,10 +5331,11 @@
return mPendingAnimation;
}
- protected void onTaskLaunchAnimationEnd(boolean success) {
+ protected Unit onTaskLaunchAnimationEnd(boolean success) {
if (success) {
resetTaskVisuals();
}
+ return Unit.INSTANCE;
}
@Override
@@ -5355,7 +5425,7 @@
}
RemoteTargetGluer gluer;
- if (isDesktopModeSupported() && recentsAnimationTargets.hasDesktopTasks()) {
+ if (recentsAnimationTargets.hasDesktopTasks()) {
gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets,
true /* forDesktop */);
mRemoteTargetHandles = gluer.assignTargetsForDesktop(recentsAnimationTargets);
@@ -5378,7 +5448,7 @@
TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
tvs.setOrientationState(mOrientationState);
- tvs.setDp(mActivity.getDeviceProfile());
+ tvs.setDp(mContainer.getDeviceProfile());
tvs.recentsViewScale.value = 1;
});
@@ -5410,6 +5480,10 @@
finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete);
}
+ /**
+ * NOTE: Whatever value gets passed through to the toRecents param may need to also be set on
+ * {@link #mRecentsAnimationController#setWillFinishToHome}.
+ */
public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
@Nullable Runnable onFinishComplete) {
Log.d(TAG, "finishRecentsAnimation - mRecentsAnimationController: "
@@ -5429,7 +5503,7 @@
// Notify the SysUI to use fade-in animation when entering PiP from live tile.
final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext());
systemUiProxy.setPipAnimationTypeToAlpha();
- systemUiProxy.setShelfHeight(true, mActivity.getDeviceProfile().hotseatBarSizePx);
+ systemUiProxy.setShelfHeight(true, mContainer.getDeviceProfile().hotseatBarSizePx);
// Transaction to hide the task to avoid flicker for entering PiP from split-screen.
// See also {@link AbsSwipeUpHandler#maybeFinishSwipeToHome}.
PictureInPictureSurfaceTransaction tx =
@@ -5455,6 +5529,9 @@
* Called when a running recents animation has finished or canceled.
*/
public void onRecentsAnimationComplete() {
+ Log.d(TAG, "onRecentsAnimationComplete "
+ + "- mRecentsAnimationController: " + mRecentsAnimationController
+ + ", mSideTaskLaunchCallback: " + mSideTaskLaunchCallback);
// At this point, the recents animation is not running and if the animation was canceled
// by a display rotation then reset this state to show the screenshot
setRunningTaskViewShowScreenshot(true);
@@ -5498,7 +5575,7 @@
protected int getClearAllExtraPageSpacing() {
return showAsGrid()
- ? Math.max(mActivity.getDeviceProfile().overviewGridSideMargin - mPageSpacing, 0)
+ ? Math.max(mContainer.getDeviceProfile().overviewGridSideMargin - mPageSpacing, 0)
: 0;
}
@@ -5530,10 +5607,6 @@
}
private int getFirstViewIndex() {
- if (isDesktopModeSupported() && mDesktopTaskView != null) {
- // Desktop task is at position 0, that is the first view
- return 0;
- }
TaskView focusedTaskView = mShowAsGridLastOnLayout ? getFocusedTaskView() : null;
return focusedTaskView != null ? indexOfChild(focusedTaskView) : 0;
}
@@ -5878,16 +5951,15 @@
return;
}
- taskView.setShowScreenshot(true);
- for (TaskIdAttributeContainer container :
- taskView.getTaskIdAttributeContainers()) {
+ setRunningTaskViewShowScreenshot(true);
+ for (TaskContainer container : taskView.getTaskContainers()) {
if (container == null) {
continue;
}
ThumbnailData td =
mRecentsAnimationController.screenshotTask(container.getTask().key.id);
- TaskThumbnailView thumbnailView = container.getThumbnailView();
+ TaskThumbnailViewDeprecated thumbnailView = container.getThumbnailViewDeprecated();
if (td != null) {
thumbnailView.setThumbnail(container.getTask(), td);
} else {
@@ -5908,7 +5980,7 @@
Runnable onFinishRunnable) {
final TaskView taskView = getRunningTaskView();
if (taskView != null) {
- taskView.setShowScreenshot(true);
+ taskView.setShouldShowScreenshot(true);
taskView.refreshThumbnails(thumbnailDatas);
ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
} else {
@@ -5951,7 +6023,7 @@
return mTaskOverlayFactory;
}
- public BaseActivityInterface getSizeStrategy() {
+ public BaseContainerInterface getSizeStrategy() {
return mSizeStrategy;
}
@@ -5983,7 +6055,7 @@
requireTaskViewAt(i).setColorTint(mColorTint, mTintingColor);
}
- Drawable scrimBg = mActivity.getScrimView().getBackground();
+ Drawable scrimBg = mContainer.getScrimView().getBackground();
if (scrimBg != null) {
if (tintAmount == 0f) {
scrimBg.setTintList(null);
@@ -6002,8 +6074,8 @@
/** Returns {@code true} if the overview tasks are displayed as a grid. */
public boolean showAsGrid() {
return mOverviewGridEnabled || (mCurrentGestureEndTarget != null
- && mSizeStrategy.stateFromGestureEndTarget(
- mCurrentGestureEndTarget).displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+ && mSizeStrategy.stateFromGestureEndTarget(mCurrentGestureEndTarget)
+ .displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
}
private boolean showAsFullscreen() {
@@ -6012,7 +6084,8 @@
}
public void cleanupRemoteTargets() {
- Log.d(TAG, "cleanupRemoteTargets");
+ Log.d(TAG, "cleanupRemoteTargets - mRemoteTargetHandles: " + Arrays.toString(
+ mRemoteTargetHandles));
mRemoteTargetHandles = null;
}
@@ -6169,7 +6242,7 @@
}
}
- private static class PinnedStackAnimationListener<T extends BaseActivity> extends
+ private static class PinnedStackAnimationListener<T extends RecentsViewContainer> extends
IPipAnimationListener.Stub {
@Nullable
private T mActivity;
@@ -6235,7 +6308,7 @@
public void updateLocusId() {
String locusId = "Overview";
- if (mOverviewStateEnabled && mActivity.isStarted()) {
+ if (mOverviewStateEnabled && mContainer.isStarted()) {
locusId += "|ENABLED";
} else {
locusId += "|DISABLED";
@@ -6243,7 +6316,48 @@
final LocusId id = new LocusId(locusId);
// Set locus context is a binder call, don't want it to happen during a transition
- UI_HELPER_EXECUTOR.post(() -> mActivity.setLocusContext(id, Bundle.EMPTY));
+ UI_HELPER_EXECUTOR.post(() -> mContainer.setLocusContext(id, Bundle.EMPTY));
+ }
+
+ /**
+ * Moves the provided task into desktop mode, and invoke {@code successCallback} if succeeded.
+ */
+ public void moveTaskToDesktop(TaskContainer taskContainer,
+ DesktopModeTransitionSource transitionSource,
+ Runnable successCallback) {
+ if (!DesktopModeStatus.canEnterDesktopMode(mContext)) {
+ return;
+ }
+ switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false,
+ () -> moveTaskToDesktopInternal(taskContainer, successCallback, transitionSource)));
+ }
+
+ private void moveTaskToDesktopInternal(TaskContainer taskContainer,
+ Runnable successCallback, DesktopModeTransitionSource transitionSource) {
+ if (mDesktopRecentsTransitionController == null) {
+ return;
+ }
+ mDesktopRecentsTransitionController.moveToDesktop(taskContainer.getTask().key.id,
+ transitionSource);
+ successCallback.run();
+ }
+
+ // Logs when the orientation of Overview changes. We log both real and fake orientation changes.
+ private void logOrientationChanged() {
+ // Only log when Overview is showing.
+ if (mOverviewStateEnabled) {
+ mContainer.getStatsLogManager()
+ .logger()
+ .withContainerInfo(
+ LauncherAtom.ContainerInfo.newBuilder()
+ .setTaskSwitcherContainer(
+ LauncherAtom.TaskSwitcherContainer.newBuilder()
+ .setOrientationHandler(
+ getPagedOrientationHandler()
+ .getHandlerTypeForLogging()))
+ .build())
+ .log(LAUNCHER_OVERVIEW_ORIENTATION_CHANGED);
+ }
}
public interface TaskLaunchListener {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
new file mode 100644
index 0000000..060c71e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -0,0 +1,201 @@
+/*
+ * 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.views;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.LocusId;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.util.SystemUiController;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.ScrimView;
+
+/**
+ * Interface to be implemented by the parent view of RecentsView
+ */
+public interface RecentsViewContainer extends ActivityContext {
+
+ /**
+ * Returns an instance of an implementation of RecentsViewContainer
+ * @param context will find instance of recentsViewContainer from given context.
+ */
+ static <T extends RecentsViewContainer> T containerFromContext(Context context) {
+ if (context instanceof RecentsViewContainer) {
+ return (T) context;
+ } else if (context instanceof ContextWrapper) {
+ return containerFromContext(((ContextWrapper) context).getBaseContext());
+ } else {
+ throw new IllegalArgumentException("Cannot find RecentsViewContainer in parent tree");
+ }
+ }
+
+ /**
+ * Returns {@link SystemUiController} to manage various window flags to control system UI.
+ */
+ SystemUiController getSystemUiController();
+
+ /**
+ * Returns {@link ScrimView}
+ */
+ ScrimView getScrimView();
+
+ /**
+ * Returns the Overview Panel as a View
+ */
+ <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);
+
+ /**
+ * @see Window.Callback#dispatchKeyEvent(KeyEvent)
+ */
+ boolean dispatchKeyEvent(KeyEvent ev);
+
+ /**
+ * Returns overview actions view as a view
+ */
+ View getActionsView();
+
+ /**
+ * @see BaseActivity#addForceInvisibleFlag(int)
+ * @param flag {@link BaseActivity.InvisibilityFlags}
+ */
+ void addForceInvisibleFlag(@BaseActivity.InvisibilityFlags int flag);
+
+ /**
+ * @see BaseActivity#clearForceInvisibleFlag(int)
+ * @param flag {@link BaseActivity.InvisibilityFlags}
+ */
+ void clearForceInvisibleFlag(@BaseActivity.InvisibilityFlags int flag);
+
+ /**
+ * @see android.app.Activity#setLocusContext(LocusId, Bundle)
+ * @param id {@link LocusId}
+ * @param bundle {@link Bundle}
+ */
+ void setLocusContext(LocusId id, Bundle bundle);
+
+ /**
+ * @see BaseActivity#isStarted()
+ * @return boolean
+ */
+ boolean isStarted();
+
+ /**
+ * @see BaseActivity#addEventCallback(int, Runnable)
+ * @param event {@link BaseActivity.ActivityEvent}
+ * @param callback runnable to be executed upon event
+ */
+ void addEventCallback(@BaseActivity.ActivityEvent int event, Runnable callback);
+
+ /**
+ * @see BaseActivity#removeEventCallback(int, Runnable)
+ * @param event {@link BaseActivity.ActivityEvent}
+ * @param callback runnable to be executed upon event
+ */
+ void removeEventCallback(@BaseActivity.ActivityEvent int event, Runnable callback);
+
+ /**
+ * @see com.android.quickstep.util.TISBindHelper#runOnBindToTouchInteractionService(Runnable)
+ * @param r runnable to be executed upon event
+ */
+ void runOnBindToTouchInteractionService(Runnable r);
+
+ /**
+ * @see Activity#getWindow()
+ * @return Window
+ */
+ Window getWindow();
+
+ /**
+ * @see
+ * BaseActivity#addMultiWindowModeChangedListener(BaseActivity.MultiWindowModeChangedListener)
+ * @param listener {@link BaseActivity.MultiWindowModeChangedListener}
+ */
+ void addMultiWindowModeChangedListener(
+ BaseActivity.MultiWindowModeChangedListener listener);
+
+ /**
+ * @see
+ * BaseActivity#removeMultiWindowModeChangedListener(
+ * BaseActivity.MultiWindowModeChangedListener)
+ * @param listener {@link BaseActivity.MultiWindowModeChangedListener}
+ */
+ void removeMultiWindowModeChangedListener(
+ BaseActivity.MultiWindowModeChangedListener listener);
+
+ /**
+ * Begins transition from overview back to homescreen
+ */
+ void returnToHomescreen();
+
+ /**
+ * True if the overview panel is visible.
+ * @return Boolean
+ */
+ boolean isRecentsViewVisible();
+
+ /**
+ * Overwrites any logged item in Launcher that doesn't have a container with the
+ * {@link com.android.launcher3.touch.PagedOrientationHandler} in use for Overview.
+ *
+ * @param itemInfoBuilder {@link LauncherAtom.ItemInfo.Builder}
+ */
+ default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) {
+ if (!itemInfoBuilder.getContainerInfo().hasTaskSwitcherContainer()) {
+ return;
+ }
+
+ if (!isRecentsViewVisible()) {
+ return;
+ }
+
+ RecentsView<?, ?> recentsView = getOverviewPanel();
+ var orientationForLogging =
+ recentsView.getPagedOrientationHandler().getHandlerTypeForLogging();
+ itemInfoBuilder.setContainerInfo(
+ LauncherAtom.ContainerInfo.newBuilder()
+ .setTaskSwitcherContainer(
+ LauncherAtom.TaskSwitcherContainer.newBuilder()
+ .setOrientationHandler(orientationForLogging))
+ .build());
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index 6a59ab4..3d994e8 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -16,13 +16,18 @@
package com.android.quickstep.views;
+import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON;
+import static com.android.settingslib.widget.theme.R.dimen.settingslib_preferred_minimum_touch_target;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.content.Context;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.view.TouchDelegate;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -33,12 +38,13 @@
import androidx.dynamicanimation.animation.SpringForce;
import com.android.app.animation.Interpolators;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.states.StateAnimationConfig;
import com.android.quickstep.util.SplitSelectStateController;
/**
@@ -51,8 +57,9 @@
public class SplitInstructionsView extends LinearLayout {
private static final int BOUNCE_DURATION = 250;
private static final float BOUNCE_HEIGHT = 20;
+ private static final int DURATION_DEFAULT_SPLIT_DISMISS = 350;
- private final StatefulActivity mLauncher;
+ private final RecentsViewContainer mContainer;
public boolean mIsCurrentlyAnimating = false;
public static final FloatProperty<SplitInstructionsView> UNFOLD =
@@ -91,13 +98,13 @@
public SplitInstructionsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mLauncher = (StatefulActivity) context;
+ mContainer = RecentsViewContainer.containerFromContext(context);
}
- public static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) {
- ViewGroup dragLayer = launcher.getDragLayer();
+ public static SplitInstructionsView getSplitInstructionsView(RecentsViewContainer container) {
+ ViewGroup dragLayer = container.getDragLayer();
final SplitInstructionsView splitInstructionsView =
- (SplitInstructionsView) launcher.getLayoutInflater().inflate(
+ (SplitInstructionsView) container.getLayoutInflater().inflate(
R.layout.split_instructions_view,
dragLayer,
false
@@ -127,6 +134,28 @@
cancelTextView.setVisibility(VISIBLE);
cancelTextView.setOnClickListener((v) -> exitSplitSelection());
instructionTextView.setText(R.string.toast_contextual_split_select_app);
+
+ // After layout, expand touch target of cancel button to meet minimum a11y measurements.
+ post(() -> {
+ int minTouchSize = getResources()
+ .getDimensionPixelSize(settingslib_preferred_minimum_touch_target);
+ Rect r = new Rect();
+ cancelTextView.getHitRect(r);
+
+ if (r.width() < minTouchSize) {
+ // add 1 to ensure ceiling on int division
+ int expandAmount = (minTouchSize + 1 - r.width()) / 2;
+ r.left -= expandAmount;
+ r.right += expandAmount;
+ }
+ if (r.height() < minTouchSize) {
+ int expandAmount = (minTouchSize + 1 - r.height()) / 2;
+ r.top -= expandAmount;
+ r.bottom += expandAmount;
+ }
+
+ setTouchDelegate(new TouchDelegate(r, cancelTextView));
+ });
}
// Set accessibility title, will be announced by a11y tools.
@@ -134,19 +163,34 @@
}
private void exitSplitSelection() {
- SplitSelectStateController splitSelectController =
- ((RecentsView) mLauncher.getOverviewPanel()).getSplitSelectController();
+ RecentsView recentsView = mContainer.getOverviewPanel();
+ SplitSelectStateController splitSelectController = recentsView.getSplitSelectController();
- splitSelectController.getSplitAnimationController().playPlaceholderDismissAnim(mLauncher,
- LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON);
- mLauncher.getStateManager().goToState(LauncherState.NORMAL);
+ StateManager stateManager = recentsView.getStateManager();
+ BaseState startState = stateManager.getState();
+ long duration = startState.getTransitionDuration(mContainer.asContext(), false);
+ if (duration == 0) {
+ // Case where we're in contextual on workspace (NORMAL), which by default has 0
+ // transition duration
+ duration = DURATION_DEFAULT_SPLIT_DISMISS;
+ }
+ StateAnimationConfig config = new StateAnimationConfig();
+ config.duration = duration;
+ AnimatorSet stateAnim = stateManager.createAtomicAnimation(
+ startState, NORMAL, config);
+ AnimatorSet dismissAnim = splitSelectController.getSplitAnimationController()
+ .createPlaceholderDismissAnim(mContainer,
+ LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON, duration);
+ stateAnim.play(dismissAnim);
+ stateManager.setCurrentAnimation(stateAnim, NORMAL);
+ stateAnim.start();
}
void ensureProperRotation() {
- ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler()
+ ((RecentsView) mContainer.getOverviewPanel()).getPagedOrientationHandler()
.setSplitInstructionsParams(
this,
- mLauncher.getDeviceProfile(),
+ mContainer.getDeviceProfile(),
getMeasuredHeight(),
getMeasuredWidth()
);
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index c9aad1a..eda58c5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -18,11 +18,9 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
+import static com.android.quickstep.views.TaskThumbnailViewDeprecated.DIM_ALPHA;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -46,7 +44,6 @@
import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -57,7 +54,7 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.TaskCornerRadius;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
/**
* Contains options for a recent task when long-pressing its icon.
@@ -69,7 +66,7 @@
private static final int REVEAL_OPEN_DURATION = enableOverviewIconMenu() ? 417 : 150;
private static final int REVEAL_CLOSE_DURATION = enableOverviewIconMenu() ? 333 : 100;
- private BaseDraggingActivity mActivity;
+ private RecentsViewContainer mContainer;
private TextView mTaskName;
@Nullable
private AnimatorSet mOpenCloseAnimator;
@@ -77,7 +74,7 @@
private ValueAnimator mRevealAnimator;
@Nullable private Runnable mOnClosingStartCallback;
private TaskView mTaskView;
- private TaskIdAttributeContainer mTaskContainer;
+ private TaskContainer mTaskContainer;
private LinearLayout mOptionLayout;
private float mMenuTranslationYBeforeOpen;
private float mMenuTranslationXBeforeOpen;
@@ -89,7 +86,7 @@
public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mActivity = BaseDraggingActivity.fromContext(context);
+ mContainer = RecentsViewContainer.containerFromContext(context);
setClipToOutline(true);
}
@@ -103,7 +100,7 @@
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- BaseDragLayer dl = mActivity.getDragLayer();
+ BaseDragLayer dl = mContainer.getDragLayer();
if (!dl.isEventOverView(this, ev)) {
// TODO: log this once we have a new container type for it?
close(true);
@@ -115,11 +112,7 @@
@Override
protected void handleClose(boolean animate) {
- if (animate || enableOverviewIconMenu()) {
- animateClose();
- } else {
- closeComplete();
- }
+ animateClose();
}
@Override
@@ -140,9 +133,14 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int maxMenuHeight = calculateMaxHeight();
- if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST);
+ if (!(enableOverviewIconMenu()
+ && ((RecentsView) mContainer.getOverviewPanel()).isOnGridBottomRow(mTaskView))) {
+ // TODO(b/326952853): Cap menu height for grid bottom row in a way that doesn't break
+ // additionalTranslationY.
+ int maxMenuHeight = calculateMaxHeight();
+ if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST);
+ }
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@@ -159,25 +157,31 @@
}
}
- public static boolean showForTask(TaskIdAttributeContainer taskContainer,
+ /**
+ * Show a task menu for the given taskContainer.
+ */
+ public static boolean showForTask(TaskContainer taskContainer,
@Nullable Runnable onClosingStartCallback) {
- BaseDraggingActivity activity = BaseDraggingActivity.fromContext(
+ RecentsViewContainer container = RecentsViewContainer.containerFromContext(
taskContainer.getTaskView().getContext());
- final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate(
- R.layout.task_menu, activity.getDragLayer(), false);
+ final TaskMenuView taskMenuView = (TaskMenuView) container.getLayoutInflater().inflate(
+ R.layout.task_menu, container.getDragLayer(), false);
taskMenuView.setOnClosingStartCallback(onClosingStartCallback);
return taskMenuView.populateAndShowForTask(taskContainer);
}
- public static boolean showForTask(TaskIdAttributeContainer taskContainer) {
+ /**
+ * Show a task menu for the given taskContainer.
+ */
+ public static boolean showForTask(TaskContainer taskContainer) {
return showForTask(taskContainer, null);
}
- private boolean populateAndShowForTask(TaskIdAttributeContainer taskContainer) {
+ private boolean populateAndShowForTask(TaskContainer taskContainer) {
if (isAttachedToWindow()) {
return false;
}
- mActivity.getDragLayer().addView(this);
+ mContainer.getDragLayer().addView(this);
mTaskView = taskContainer.getTaskView();
mTaskContainer = taskContainer;
if (!populateAndLayoutMenu()) {
@@ -189,16 +193,12 @@
/** @return true if successfully able to populate task view menu, false otherwise */
private boolean populateAndLayoutMenu() {
- if (mTaskContainer.getTask().icon == null) {
- // Icon may not be loaded
- return false;
- }
addMenuOptions(mTaskContainer);
orientAroundTaskView(mTaskContainer);
return true;
}
- private void addMenuOptions(TaskIdAttributeContainer taskContainer) {
+ private void addMenuOptions(TaskContainer taskContainer) {
if (enableOverviewIconMenu()) {
removeView(mTaskName);
} else {
@@ -210,7 +210,7 @@
}
private void addMenuOption(SystemShortcut menuOption) {
- LinearLayout menuOptionView = (LinearLayout) mActivity.getLayoutInflater().inflate(
+ LinearLayout menuOptionView = (LinearLayout) mContainer.getLayoutInflater().inflate(
R.layout.task_view_menu_option, this, false);
if (enableOverviewIconMenu()) {
((GradientDrawable) menuOptionView.getBackground()).setCornerRadius(0);
@@ -219,30 +219,31 @@
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp,
- menuOptionView, mActivity.getDeviceProfile());
+ menuOptionView, mContainer.getDeviceProfile());
// Set an onClick listener on each menu option. The onClick method is responsible for
// ending LiveTile mode on the thumbnail if needed.
menuOptionView.setOnClickListener(menuOption::onClick);
mOptionLayout.addView(menuOptionView);
}
- private void orientAroundTaskView(TaskIdAttributeContainer taskContainer) {
- RecentsView recentsView = mActivity.getOverviewPanel();
+ private void orientAroundTaskView(TaskContainer taskContainer) {
+ RecentsView recentsView = mContainer.getOverviewPanel();
RecentsPagedOrientationHandler orientationHandler =
recentsView.getPagedOrientationHandler();
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
// Get Position
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- mActivity.getDragLayer().getDescendantRectRelativeToSelf(
+ DeviceProfile deviceProfile = mContainer.getDeviceProfile();
+ mContainer.getDragLayer().getDescendantRectRelativeToSelf(
enableOverviewIconMenu()
? getIconView().findViewById(R.id.icon_view_menu_anchor)
- : taskContainer.getThumbnailView(),
+ : taskContainer.getThumbnailViewDeprecated(),
sTempRect);
- Rect insets = mActivity.getDragLayer().getInsets();
+ Rect insets = mContainer.getDragLayer().getInsets();
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
- params.width = orientationHandler.getTaskMenuWidth(taskContainer.getThumbnailView(),
- deviceProfile, taskContainer.getStagePosition());
+ params.width = orientationHandler.getTaskMenuWidth(
+ taskContainer.getThumbnailViewDeprecated(), deviceProfile,
+ taskContainer.getStagePosition());
// Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
params.gravity = Gravity.LEFT;
setLayoutParams(params);
@@ -275,10 +276,10 @@
// Margin that insets the menuView inside the taskView
float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX,
- mTaskContainer.getThumbnailView(), deviceProfile, taskInsetMargin,
+ mTaskContainer.getThumbnailViewDeprecated(), deviceProfile, taskInsetMargin,
getIconView()));
setTranslationY(orientationHandler.getTaskMenuY(
- thumbnailAlignedY, mTaskContainer.getThumbnailView(),
+ thumbnailAlignedY, mTaskContainer.getThumbnailViewDeprecated(),
mTaskContainer.getStagePosition(), this, taskInsetMargin,
getIconView()));
}
@@ -319,12 +320,12 @@
IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView();
float additionalTranslationY = 0;
- if (((RecentsView) mActivity.getOverviewPanel()).isOnGridBottomRow(mTaskView)) {
+ if (((RecentsView) mContainer.getOverviewPanel()).isOnGridBottomRow(mTaskView)) {
// Animate menu up for enough room to display full menu when task on bottom row.
float menuBottom = getHeight() + mMenuTranslationYBeforeOpen;
float taskBottom = mTaskView.getHeight() + mTaskView.getPersistentTranslationY();
- float taskbarTop = mActivity.getDeviceProfile().heightPx
- - mActivity.getDeviceProfile().getOverviewActionsClaimedSpaceBelow();
+ float taskbarTop = mContainer.getDeviceProfile().heightPx
+ - mContainer.getDeviceProfile().getOverviewActionsClaimedSpaceBelow();
float midpoint = (taskBottom + taskbarTop) / 2f;
additionalTranslationY = -Math.max(menuBottom - midpoint, 0);
}
@@ -339,11 +340,11 @@
menuTranslationYAnim.setInterpolator(EMPHASIZED);
float additionalTranslationX = 0;
- if (mActivity.getDeviceProfile().isLandscape
+ if (mContainer.getDeviceProfile().isLandscape
&& mTaskContainer.getStagePosition() == STAGE_POSITION_BOTTOM_OR_RIGHT) {
// Animate menu and icon when split task would display off the side of the screen.
additionalTranslationX = Math.max(
- getTranslationX() + getWidth() - (mActivity.getDeviceProfile().widthPx
+ getTranslationX() + getWidth() - (mContainer.getDeviceProfile().widthPx
- getResources().getDimensionPixelSize(
R.dimen.task_menu_edge_padding) * 2), 0);
}
@@ -361,17 +362,14 @@
mOpenCloseAnimator.playTogether(translationYAnim, translationXAnim,
menuTranslationXAnim, menuTranslationYAnim);
}
-
mOpenCloseAnimator.playTogether(mRevealAnimator,
ObjectAnimator.ofFloat(
- mTaskContainer.getThumbnailView(), DIM_ALPHA,
+ mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA,
closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA),
ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
- testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE,
- "TaskMenuView.java.animateOpenOrClosed: onAnimationStart");
setVisibility(VISIBLE);
if (closing && mOnClosingStartCallback != null) {
mOnClosingStartCallback.run();
@@ -380,8 +378,6 @@
@Override
public void onAnimationSuccess(Animator animator) {
- testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE,
- "TaskMenuView.java.animateOpenOrClosed: onAnimationSuccess");
if (closing) {
closeComplete();
}
@@ -393,7 +389,7 @@
private void closeComplete() {
mIsOpen = false;
- mActivity.getDragLayer().removeView(this);
+ mContainer.getDragLayer().removeView(this);
mRevealAnimator = null;
}
@@ -416,7 +412,7 @@
private int calculateMaxHeight() {
float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
return mTaskView.getPagedOrientationHandler().getTaskMenuHeight(taskInsetMargin,
- mActivity.getDeviceProfile(), getTranslationX(), getTranslationY());
+ mContainer.getDeviceProfile(), getTranslationX(), getTranslationY());
}
private void setOnClosingStartCallback(Runnable onClosingStartCallback) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 12b8b6f..659cc0c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -29,7 +29,6 @@
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
-import com.android.launcher3.BaseDraggingActivity
import com.android.launcher3.DeviceProfile
import com.android.launcher3.InsettableFrameLayout
import com.android.launcher3.R
@@ -38,24 +37,19 @@
import com.android.launcher3.popup.SystemShortcut
import com.android.launcher3.util.Themes
import com.android.quickstep.TaskOverlayFactory
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.quickstep.views.TaskView.TaskContainer
-class TaskMenuViewWithArrow<T : BaseDraggingActivity> : ArrowPopup<T> {
+class TaskMenuViewWithArrow<T> : ArrowPopup<T> where T : RecentsViewContainer, T : Context {
companion object {
const val TAG = "TaskMenuViewWithArrow"
- fun showForTask(
- taskContainer: TaskIdAttributeContainer,
- alignedOptionIndex: Int = 0
- ): Boolean {
- val activity =
- BaseDraggingActivity.fromContext<BaseDraggingActivity>(
- taskContainer.taskView.context
- )
+ fun showForTask(taskContainer: TaskContainer, alignedOptionIndex: Int = 0): Boolean {
+ val container: RecentsViewContainer =
+ RecentsViewContainer.containerFromContext(taskContainer.taskView.context)
val taskMenuViewWithArrow =
- activity.layoutInflater.inflate(
+ container.layoutInflater.inflate(
R.layout.task_menu_with_arrow,
- activity.dragLayer,
+ container.dragLayer,
false
) as TaskMenuViewWithArrow<*>
@@ -86,11 +80,11 @@
private var alignedOptionIndex: Int = 0
private val extraSpaceForRowAlignment: Int
get() = optionMeasuredHeight * alignedOptionIndex
- private val menuWidth = context.resources.getDimensionPixelSize(R.dimen.task_menu_width_grid)
+ private val menuPaddingEnd = context.resources.getDimensionPixelSize(R.dimen.task_card_margin)
private lateinit var taskView: TaskView
private lateinit var optionLayout: LinearLayout
- private lateinit var taskContainer: TaskIdAttributeContainer
+ private lateinit var taskContainer: TaskContainer
private var optionMeasuredHeight = 0
private val arrowHorizontalPadding: Int
@@ -124,8 +118,27 @@
optionLayout = requireViewById(R.id.menu_option_layout)
}
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val maxMenuHeight: Int = calculateMaxHeight()
+ val newHeightMeasureSpec =
+ if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
+ MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST)
+ } else heightMeasureSpec
+ super.onMeasure(widthMeasureSpec, newHeightMeasureSpec)
+ }
+
+ private fun calculateMaxHeight(): Int {
+ val taskInsetMargin = resources.getDimension(R.dimen.task_card_margin)
+ return taskView.pagedOrientationHandler.getTaskMenuHeight(
+ taskInsetMargin,
+ mActivityContext.deviceProfile,
+ translationX,
+ translationY
+ )
+ }
+
private fun populateAndShowForTask(
- taskContainer: TaskIdAttributeContainer,
+ taskContainer: TaskContainer,
alignedOptionIndex: Int
): Boolean {
if (isAttachedToWindow) {
@@ -158,10 +171,10 @@
/** @return true if successfully able to populate task view menu, false otherwise */
private fun populateMenu(): Boolean {
// Icon may not be loaded
- if (taskContainer.task.icon == null) return false
+ if (taskContainer.iconView.drawable == null) return false
addMenuOptions()
- return true
+ return optionLayout.childCount > 0
}
private fun addMenuOptions() {
@@ -197,7 +210,13 @@
menuOptionView.requireViewById(R.id.text)
)
val lp = menuOptionView.layoutParams as LayoutParams
- lp.width = menuWidth
+ lp.width = LayoutParams.MATCH_PARENT
+ menuOptionView.setPaddingRelative(
+ menuOptionView.paddingStart,
+ menuOptionView.paddingTop,
+ menuPaddingEnd,
+ menuOptionView.paddingBottom
+ )
menuOptionView.setOnClickListener { view: View? -> menuOption.onClick(view) }
optionLayout.addView(menuOptionView)
}
@@ -331,7 +350,7 @@
override fun updateArrowColor() {
mArrow.background =
- RoundedArrowDrawable(
+ RoundedArrowDrawable.createHorizontalRoundedArrow(
mArrowWidth.toFloat(),
mArrowHeight.toFloat(),
mArrowPointRadius.toFloat(),
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
similarity index 82%
rename from quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
rename to quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 077247b..4283d0e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -48,12 +48,12 @@
import androidx.annotation.RequiresApi;
import androidx.core.graphics.ColorUtils;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
+import com.android.launcher3.util.ViewPool;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.views.TaskView.FullscreenDrawParams;
@@ -61,70 +61,76 @@
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
+import java.util.Objects;
+
/**
* A task in the Recents view.
+ *
+ * @deprecated This class will be replaced by the new [TaskThumbnailView].
*/
-public class TaskThumbnailView extends View {
+@Deprecated
+public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusable {
private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
new MainThreadInitializedObject<>(FullscreenDrawParams::new);
- public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
- new FloatProperty<TaskThumbnailView>("dimAlpha") {
+ public static final Property<TaskThumbnailViewDeprecated, Float> DIM_ALPHA =
+ new FloatProperty<TaskThumbnailViewDeprecated>("dimAlpha") {
@Override
- public void setValue(TaskThumbnailView thumbnail, float dimAlpha) {
+ public void setValue(TaskThumbnailViewDeprecated thumbnail, float dimAlpha) {
thumbnail.setDimAlpha(dimAlpha);
}
@Override
- public Float get(TaskThumbnailView thumbnailView) {
+ public Float get(TaskThumbnailViewDeprecated thumbnailView) {
return thumbnailView.mDimAlpha;
}
};
- public static final Property<TaskThumbnailView, Float> SPLASH_ALPHA =
- new FloatProperty<TaskThumbnailView>("splashAlpha") {
+ public static final Property<TaskThumbnailViewDeprecated, Float> SPLASH_ALPHA =
+ new FloatProperty<TaskThumbnailViewDeprecated>("splashAlpha") {
@Override
- public void setValue(TaskThumbnailView thumbnail, float splashAlpha) {
+ public void setValue(TaskThumbnailViewDeprecated thumbnail, float splashAlpha) {
thumbnail.setSplashAlpha(splashAlpha);
}
@Override
- public Float get(TaskThumbnailView thumbnailView) {
+ public Float get(TaskThumbnailViewDeprecated thumbnailView) {
return thumbnailView.mSplashAlpha / 255f;
}
};
/** Use to animate thumbnail translationX while first app in split selection is initiated */
- public static final Property<TaskThumbnailView, Float> SPLIT_SELECT_TRANSLATE_X =
- new FloatProperty<TaskThumbnailView>("splitSelectTranslateX") {
+ public static final Property<TaskThumbnailViewDeprecated, Float> SPLIT_SELECT_TRANSLATE_X =
+ new FloatProperty<TaskThumbnailViewDeprecated>("splitSelectTranslateX") {
@Override
- public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateX) {
+ public void setValue(TaskThumbnailViewDeprecated thumbnail,
+ float splitSelectTranslateX) {
thumbnail.applySplitSelectTranslateX(splitSelectTranslateX);
}
@Override
- public Float get(TaskThumbnailView thumbnailView) {
+ public Float get(TaskThumbnailViewDeprecated thumbnailView) {
return thumbnailView.mSplitSelectTranslateX;
}
};
/** Use to animate thumbnail translationY while first app in split selection is initiated */
- public static final Property<TaskThumbnailView, Float> SPLIT_SELECT_TRANSLATE_Y =
- new FloatProperty<TaskThumbnailView>("splitSelectTranslateY") {
+ public static final Property<TaskThumbnailViewDeprecated, Float> SPLIT_SELECT_TRANSLATE_Y =
+ new FloatProperty<TaskThumbnailViewDeprecated>("splitSelectTranslateY") {
@Override
- public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateY) {
+ public void setValue(TaskThumbnailViewDeprecated thumbnail,
+ float splitSelectTranslateY) {
thumbnail.applySplitSelectTranslateY(splitSelectTranslateY);
}
@Override
- public Float get(TaskThumbnailView thumbnailView) {
+ public Float get(TaskThumbnailViewDeprecated thumbnailView) {
return thumbnailView.mSplitSelectTranslateY;
}
};
- private final BaseActivity mActivity;
- @Nullable
- private TaskOverlay mOverlay;
+ private final RecentsViewContainer mContainer;
+ private TaskOverlay<?> mOverlay;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mSplashBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -157,21 +163,22 @@
private float mSplitSelectTranslateX;
private float mSplitSelectTranslateY;
- public TaskThumbnailView(Context context) {
+ public TaskThumbnailViewDeprecated(Context context) {
this(context, null);
}
- public TaskThumbnailView(Context context, @Nullable AttributeSet attrs) {
+ public TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public TaskThumbnailView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ public TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint.setFilterBitmap(true);
mBackgroundPaint.setColor(Color.WHITE);
mSplashBackgroundPaint.setColor(Color.WHITE);
mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
- mActivity = BaseActivity.fromContext(context);
+ mContainer = RecentsViewContainer.containerFromContext(context);
// Initialize with placeholder value. It is overridden later by TaskView
mFullscreenParams = TEMP_PARAMS.get(context);
@@ -181,10 +188,10 @@
/**
* Updates the thumbnail to draw the provided task
- * @param task
*/
- public void bind(Task task) {
- getTaskOverlay().reset();
+ public void bind(Task task, TaskOverlay<?> overlay) {
+ mOverlay = overlay;
+ mOverlay.reset();
mTask = task;
int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
mPaint.setColor(color);
@@ -194,7 +201,19 @@
}
/**
+ * Sets TaskOverlay without binding a task.
+ *
+ * @deprecated Should only be used when using new
+ * {@link com.android.quickstep.task.thumbnail.TaskThumbnailView}.
+ */
+ @Deprecated
+ public void setTaskOverlay(TaskOverlay<?> overlay) {
+ mOverlay = overlay;
+ }
+
+ /**
* Updates the thumbnail.
+ *
* @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately.
* In most cases, we use the {@link #setThumbnail(Task, ThumbnailData)}
* version with {@code refreshNow} is true. The only exception is
@@ -205,14 +224,16 @@
public void setThumbnail(@Nullable Task task, @Nullable ThumbnailData thumbnailData,
boolean refreshNow) {
mTask = task;
- boolean thumbnailWasNull = mThumbnailData == null;
- mThumbnailData =
- (thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null;
+ ThumbnailData oldThumbnailData = mThumbnailData;
+ mThumbnailData = (thumbnailData != null && thumbnailData.getThumbnail() != null)
+ ? thumbnailData : null;
if (mTask != null) {
updateSplashView(mTask.icon);
}
if (refreshNow) {
- refresh(thumbnailWasNull && mThumbnailData != null);
+ Long oldSnapshotId = oldThumbnailData != null ? oldThumbnailData.getSnapshotId() : null;
+ Long snapshotId = mThumbnailData != null ? mThumbnailData.getSnapshotId() : null;
+ refresh(snapshotId != null && !Objects.equals(oldSnapshotId, snapshotId));
}
}
@@ -228,11 +249,12 @@
/**
* Updates the shader, paint, matrix to redraw.
+ *
* @param shouldRefreshOverlay whether to re-initialize overlay
*/
private void refresh(boolean shouldRefreshOverlay) {
- if (mThumbnailData != null && mThumbnailData.thumbnail != null) {
- Bitmap bm = mThumbnailData.thumbnail;
+ if (mThumbnailData != null && mThumbnailData.getThumbnail() != null) {
+ Bitmap bm = mThumbnailData.getThumbnail();
bm.prepareToDraw();
mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(mBitmapShader);
@@ -244,7 +266,7 @@
mBitmapShader = null;
mThumbnailData = null;
mPaint.setShader(null);
- getTaskOverlay().reset();
+ mOverlay.reset();
}
updateThumbnailPaintFilter();
}
@@ -254,7 +276,6 @@
* <p>
* If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be the
* extracted background color.
- *
*/
public void setDimAlpha(float dimAlpha) {
mDimAlpha = dimAlpha;
@@ -273,13 +294,6 @@
invalidate();
}
- public TaskOverlay getTaskOverlay() {
- if (mOverlay == null) {
- mOverlay = getTaskView().getRecentsView().getTaskOverlayFactory().createOverlay(this);
- }
- return mOverlay;
- }
-
public float getDimAlpha() {
return mDimAlpha;
}
@@ -287,6 +301,7 @@
/**
* Get the scaled insets that are being used to draw the task view. This is a subsection of
* the full snapshot.
+ *
* @return the insets in snapshot bitmap coordinates.
*/
@RequiresApi(api = Build.VERSION_CODES.Q)
@@ -296,8 +311,10 @@
}
RectF bitmapRect = new RectF(
- 0, 0,
- mThumbnailData.thumbnail.getWidth(), mThumbnailData.thumbnail.getHeight());
+ 0,
+ 0,
+ mThumbnailData.getThumbnail().getWidth(),
+ mThumbnailData.getThumbnail().getHeight());
RectF viewRect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
// The position helper matrix tells us how to transform the bitmap to fit the view, the
@@ -308,7 +325,7 @@
RectF boundsInBitmapSpace = new RectF();
boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
- DeviceProfile dp = mActivity.getDeviceProfile();
+ DeviceProfile dp = mContainer.getDeviceProfile();
int bottomInset = dp.isTablet
? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0;
return Insets.of(0, 0, 0, bottomInset);
@@ -341,7 +358,7 @@
canvas.save();
// Draw the insets if we're being drawn fullscreen (we do this for quick switch).
drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(),
- mFullscreenParams.mCurrentDrawnCornerRadius);
+ mFullscreenParams.getCurrentDrawnCornerRadius());
canvas.restore();
}
@@ -351,13 +368,13 @@
public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
mFullscreenParams = fullscreenParams;
- getTaskOverlay().setFullscreenParams(fullscreenParams);
invalidate();
}
public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
float cornerRadius) {
- if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
+ if (mTask != null && getTaskView().isRunningTask()
+ && !getTaskView().getShouldShowScreenshot()) {
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
mDimmingPaintAfterClearing);
@@ -495,13 +512,13 @@
}
private boolean isThumbnailAspectRatioDifferentFromThumbnailData() {
- if (mThumbnailData == null || mThumbnailData.thumbnail == null) {
+ if (mThumbnailData == null || mThumbnailData.getThumbnail() == null) {
return false;
}
float thumbnailViewAspect = getWidth() / (float) getHeight();
- float thumbnailDataAspect =
- mThumbnailData.thumbnail.getWidth() / (float) mThumbnailData.thumbnail.getHeight();
+ float thumbnailDataAspect = mThumbnailData.getThumbnail().getWidth()
+ / (float) mThumbnailData.getThumbnail().getHeight();
return isRelativePercentDifferenceGreaterThan(thumbnailViewAspect,
thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
@@ -527,10 +544,10 @@
*/
private void refreshOverlay() {
if (mOverlayEnabled) {
- getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(),
+ mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(),
mPreviewPositionHelper.isOrientationChanged());
} else {
- getTaskOverlay().reset();
+ mOverlay.reset();
}
}
@@ -549,13 +566,12 @@
}
private void updateThumbnailMatrix() {
- DeviceProfile dp = mActivity.getDeviceProfile();
+ DeviceProfile dp = mContainer.getDeviceProfile();
mPreviewPositionHelper.setOrientationChanged(false);
if (mBitmapShader != null && mThumbnailData != null) {
- mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
- mThumbnailData.thumbnail.getHeight());
- int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
- .getRecentsActivityRotation();
+ mPreviewRect.set(0, 0, mThumbnailData.getThumbnail().getWidth(),
+ mThumbnailData.getThumbnail().getHeight());
+ int currentRotation = getTaskView().getOrientedState().getRecentsActivityRotation();
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
getMeasuredWidth(), getMeasuredHeight(), dp.isTablet, currentRotation, isRtl);
@@ -587,7 +603,7 @@
if (mThumbnailData == null) {
return null;
}
- return mThumbnailData.thumbnail;
+ return mThumbnailData.getThumbnail();
}
/**
@@ -600,4 +616,9 @@
}
return mThumbnailData.isRealSnapshot && !mTask.isLocked;
}
+
+ @Override
+ public void onRecycle() {
+ // Do nothing
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
deleted file mode 100644
index 085c502..0000000
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ /dev/null
@@ -1,1942 +0,0 @@
-/*
- * Copyright (C) 2017 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.views;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.widget.Toast.LENGTH_SHORT;
-
-import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
-import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
-import static com.android.quickstep.TaskOverlayFactory.getEnabledShortcuts;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
-import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
-import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.annotation.IdRes;
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.util.Log;
-import android.view.Display;
-import android.view.MotionEvent;
-import android.view.RemoteAnimationTarget;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.FrameLayout;
-import android.widget.Toast;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.app.animation.Interpolators;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.CancellableTask;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.TransformingTouchDelegate;
-import com.android.launcher3.util.ViewPool.Reusable;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.RemoteAnimationTargets;
-import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
-import com.android.quickstep.TaskAnimationManager;
-import com.android.quickstep.TaskIconCache;
-import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.TaskUtils;
-import com.android.quickstep.TaskViewUtils;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.BorderAnimator;
-import com.android.quickstep.util.RecentsOrientedState;
-import com.android.quickstep.util.SplitSelectStateController;
-import com.android.quickstep.util.TaskCornerRadius;
-import com.android.quickstep.util.TaskRemovedDuringLaunchListener;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.QuickStepContract;
-
-import kotlin.Unit;
-
-import java.lang.annotation.Retention;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-
-/**
- * A task in the Recents view.
- */
-public class TaskView extends FrameLayout implements Reusable {
-
- private static final String TAG = TaskView.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- public static final int FLAG_UPDATE_ICON = 1;
- public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
- public static final int FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL << 1;
-
- public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL
- | FLAG_UPDATE_CORNER_RADIUS;
-
- /**
- * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more
- * granularity on which components of this task require an update
- */
- @Retention(SOURCE)
- @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS})
- public @interface TaskDataChanges {}
-
- /**
- * Type of task view
- */
- @Retention(SOURCE)
- @IntDef({Type.SINGLE, Type.GROUPED, Type.DESKTOP})
- public @interface Type {
- int SINGLE = 1;
- int GROUPED = 2;
- int DESKTOP = 3;
- }
-
- /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
- public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
-
- private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
- private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
-
- public static final long SCALE_ICON_DURATION = 120;
- private static final long DIM_ANIM_DURATION = 700;
-
- /**
- * This technically can be a vanilla {@link TouchDelegate} class, however that class requires
- * setting the touch bounds at construction, so we'd repeatedly be created many instances
- * unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch
- * delegated bounds only to be updated.
- */
- private TransformingTouchDelegate mIconTouchDelegate;
-
- private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
- Collections.singletonList(new Rect());
-
- public static final FloatProperty<TaskView> FOCUS_TRANSITION =
- new FloatProperty<TaskView>("focusTransition") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setIconsAndBannersTransitionProgress(v, false /* invert */);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mFocusTransitionProgress;
- }
- };
-
- private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_X =
- new FloatProperty<TaskView>("splitSelectTranslationX") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setSplitSelectTranslationX(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mSplitSelectTranslationX;
- }
- };
-
- private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_Y =
- new FloatProperty<TaskView>("splitSelectTranslationY") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setSplitSelectTranslationY(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mSplitSelectTranslationY;
- }
- };
-
- private static final FloatProperty<TaskView> DISMISS_TRANSLATION_X =
- new FloatProperty<TaskView>("dismissTranslationX") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setDismissTranslationX(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mDismissTranslationX;
- }
- };
-
- private static final FloatProperty<TaskView> DISMISS_TRANSLATION_Y =
- new FloatProperty<TaskView>("dismissTranslationY") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setDismissTranslationY(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mDismissTranslationY;
- }
- };
-
- private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_X =
- new FloatProperty<TaskView>("taskOffsetTranslationX") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setTaskOffsetTranslationX(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mTaskOffsetTranslationX;
- }
- };
-
- private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_Y =
- new FloatProperty<TaskView>("taskOffsetTranslationY") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setTaskOffsetTranslationY(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mTaskOffsetTranslationY;
- }
- };
-
- private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_X =
- new FloatProperty<TaskView>("taskResistanceTranslationX") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setTaskResistanceTranslationX(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mTaskResistanceTranslationX;
- }
- };
-
- private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_Y =
- new FloatProperty<TaskView>("taskResistanceTranslationY") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setTaskResistanceTranslationY(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mTaskResistanceTranslationY;
- }
- };
-
- public static final FloatProperty<TaskView> GRID_END_TRANSLATION_X =
- new FloatProperty<TaskView>("gridEndTranslationX") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setGridEndTranslationX(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mGridEndTranslationX;
- }
- };
-
- public static final FloatProperty<TaskView> SNAPSHOT_SCALE =
- new FloatProperty<TaskView>("snapshotScale") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setSnapshotScale(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mSnapshotView.getScaleX();
- }
- };
-
- @Nullable
- protected Task mTask;
- protected TaskThumbnailView mSnapshotView;
- protected TaskViewIcon mIconView;
- protected final DigitalWellBeingToast mDigitalWellBeingToast;
- protected float mFullscreenProgress;
- private float mGridProgress;
- protected float mTaskThumbnailSplashAlpha;
- private float mNonGridScale = 1;
- private float mDismissScale = 1;
- protected final FullscreenDrawParams mCurrentFullscreenParams;
- protected final StatefulActivity mActivity;
-
- // Various causes of changing primary translation, which we aggregate to setTranslationX/Y().
- private float mDismissTranslationX;
- private float mDismissTranslationY;
- private float mTaskOffsetTranslationX;
- private float mTaskOffsetTranslationY;
- private float mTaskResistanceTranslationX;
- private float mTaskResistanceTranslationY;
- // The following translation variables should only be used in the same orientation as Launcher.
- private float mBoxTranslationY;
- // The following grid translations scales with mGridProgress.
- private float mGridTranslationX;
- private float mGridTranslationY;
- // The following grid translation is used to animate closing the gap between grid and clear all.
- private float mGridEndTranslationX;
- // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
- // switch.
- private float mNonGridTranslationX;
- private float mNonGridPivotTranslationX;
- // Used when in SplitScreenSelectState
- private float mSplitSelectTranslationY;
- private float mSplitSelectTranslationX;
-
- @Nullable
- private ObjectAnimator mIconAndDimAnimator;
- private float mIconScaleAnimStartProgress = 0;
- private float mFocusTransitionProgress = 1;
- private float mModalness = 0;
- private float mStableAlpha = 1;
-
- private int mTaskViewId = -1;
- /**
- * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right
- */
- protected int[] mTaskIdContainer = new int[]{-1, -1};
- protected TaskIdAttributeContainer[] mTaskIdAttributeContainer =
- new TaskIdAttributeContainer[2];
-
- private boolean mShowScreenshot;
- private boolean mBorderEnabled;
-
- // The current background requests to load the task thumbnail and icon
- @Nullable
- private CancellableTask mThumbnailLoadRequest;
- @Nullable
- private CancellableTask mIconLoadRequest;
-
- private boolean mEndQuickswitchCuj;
-
- private final float[] mIconCenterCoords = new float[2];
-
- protected final PointF mLastTouchDownPosition = new PointF();
-
- private boolean mIsClickableAsLiveTile = true;
-
- @Nullable private final BorderAnimator mFocusBorderAnimator;
-
- @Nullable private final BorderAnimator mHoverBorderAnimator;
-
- public TaskView(Context context) {
- this(context, null);
- }
-
- public TaskView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- this(context, attrs, defStyleAttr, defStyleRes, null, null);
- }
-
- @VisibleForTesting
- public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes, BorderAnimator focusBorderAnimator,
- BorderAnimator hoverBorderAnimator) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mActivity = StatefulActivity.fromContext(context);
- setOnClickListener(this::onClick);
-
- mCurrentFullscreenParams = new FullscreenDrawParams(context);
- mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
-
- boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
- || isDesktopModeSupported();
- boolean cursorHoverStatesEnabled = enableCursorHoverStates();
-
- setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled);
-
- TypedArray styledAttrs = context.obtainStyledAttributes(
- attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
-
- if (focusBorderAnimator != null) {
- mFocusBorderAnimator = focusBorderAnimator;
- } else {
- mFocusBorderAnimator = keyboardFocusHighlightEnabled
- ? BorderAnimator.createSimpleBorderAnimator(
- /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR))
- : null;
- }
-
- if (hoverBorderAnimator != null) {
- mHoverBorderAnimator = hoverBorderAnimator;
- } else {
- mHoverBorderAnimator = cursorHoverStatesEnabled
- ? BorderAnimator.createSimpleBorderAnimator(
- /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.task_hover_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR))
- : null;
- }
- styledAttrs.recycle();
- }
-
- protected Unit updateBorderBounds(@NonNull Rect bounds) {
- bounds.set(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()),
- mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()),
- mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()),
- mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()));
- return Unit.INSTANCE;
- }
-
- public void setTaskViewId(int id) {
- this.mTaskViewId = id;
- }
-
- public int getTaskViewId() {
- return mTaskViewId;
- }
-
- /**
- * Builds proto for logging
- */
- public WorkspaceItemInfo getItemInfo() {
- return getItemInfo(mTask);
- }
-
- protected WorkspaceItemInfo getItemInfo(@Nullable Task task) {
- WorkspaceItemInfo stubInfo = new WorkspaceItemInfo();
- stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
- stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
- if (task == null) {
- return stubInfo;
- }
-
- ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
- stubInfo.user = componentKey.user;
- stubInfo.intent = new Intent().setComponent(componentKey.componentName);
- stubInfo.title = task.title;
- if (getRecentsView() != null) {
- stubInfo.screenId = getRecentsView().indexOfChild(this);
- }
- return stubInfo;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSnapshotView = findViewById(R.id.snapshot);
- ViewStub iconViewStub = findViewById(R.id.icon);
- if (enableOverviewIconMenu()) {
- iconViewStub.setLayoutResource(R.layout.icon_app_chip_view);
- } else {
- iconViewStub.setLayoutResource(R.layout.icon_view);
- }
- mIconView = (TaskViewIcon) iconViewStub.inflate();
- mIconTouchDelegate = new TransformingTouchDelegate(mIconView.asView());
- }
-
- @Override
- @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
- public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- if (mFocusBorderAnimator != null && mBorderEnabled) {
- mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
- }
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- if (mHoverBorderAnimator != null && mBorderEnabled) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- mHoverBorderAnimator.setBorderVisibility(/* visible= */ true, /* animated= */
- true);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- mHoverBorderAnimator.setBorderVisibility(/* visible= */ false, /* animated= */
- true);
- break;
- default:
- break;
- }
- }
- return super.onHoverEvent(event);
- }
-
- /**
- * Enable or disable showing border on hover and focus change
- */
- public void setBorderEnabled(boolean enabled) {
- mBorderEnabled = enabled;
- // Set the animation correctly in case it misses the hover/focus event during state
- // transition
- if (mHoverBorderAnimator != null) {
- mHoverBorderAnimator.setBorderVisibility(/* visible= */
- enabled && isHovered(), /* animated= */ true);
- }
-
- if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.setBorderVisibility(/* visible= */
- enabled && isFocused(), /* animated= */true);
- }
- }
-
- @Override
- public boolean onInterceptHoverEvent(MotionEvent event) {
- if (enableCursorHoverStates()) {
- // avoid triggering hover event on child elements which would cause HOVER_EXIT for this
- // task view
- return true;
- } else {
- return super.onInterceptHoverEvent(event);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- // Draw border first so any child views outside of the thumbnail bounds are drawn above it.
- if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.drawBorder(canvas);
- }
- if (mHoverBorderAnimator != null) {
- mHoverBorderAnimator.drawBorder(canvas);
- }
- super.draw(canvas);
- }
-
- /**
- * Whether the taskview should take the touch event from parent. Events passed to children
- * that might require special handling.
- */
- public boolean offerTouchToChildren(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- computeAndSetIconTouchDelegate(mIconView, mIconCenterCoords, mIconTouchDelegate);
- }
- if (mIconTouchDelegate != null && mIconTouchDelegate.onTouchEvent(event)) {
- return true;
- }
- return false;
- }
-
- protected void computeAndSetIconTouchDelegate(TaskViewIcon view, float[] tempCenterCoords,
- TransformingTouchDelegate transformingTouchDelegate) {
- if (view == null) {
- return;
- }
- float viewHalfWidth = view.getWidth() / 2f;
- float viewHalfHeight = view.getHeight() / 2f;
- tempCenterCoords[0] = viewHalfWidth;
- tempCenterCoords[1] = viewHalfHeight;
- getDescendantCoordRelativeToAncestor(view.asView(), mActivity.getDragLayer(),
- tempCenterCoords, false);
- transformingTouchDelegate.setBounds(
- (int) (tempCenterCoords[0] - viewHalfWidth),
- (int) (tempCenterCoords[1] - viewHalfHeight),
- (int) (tempCenterCoords[0] + viewHalfWidth),
- (int) (tempCenterCoords[1] + viewHalfHeight));
- }
-
- /**
- * The modalness of this view is how it should be displayed when it is shown on its own in the
- * modal state of overview.
- *
- * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own.
- */
- public void setModalness(float modalness) {
- if (mModalness == modalness) {
- return;
- }
- mModalness = modalness;
- mIconView.setModalAlpha(1 - modalness);
- mDigitalWellBeingToast.updateBannerOffset(modalness);
- }
-
- public DigitalWellBeingToast getDigitalWellBeingToast() {
- return mDigitalWellBeingToast;
- }
-
- /**
- * Updates this task view to the given {@param task}.
- *
- * TODO(b/142282126) Re-evaluate if we need to pass in isMultiWindowMode after
- * that issue is fixed
- */
- public void bind(Task task, RecentsOrientedState orientedState) {
- cancelPendingLoadTasks();
- mTask = task;
- mTaskIdContainer[0] = mTask.key.id;
- mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView, mIconView,
- STAGE_POSITION_UNDEFINED);
- mSnapshotView.bind(task);
- setOrientationState(orientedState);
- }
-
- /**
- * Sets up an on-click listener and the visibility for show_windows icon on top of the task.
- */
- public void setUpShowAllInstancesListener() {
- String taskPackageName = mTaskIdAttributeContainer[0].mTask.key.getPackageName();
-
- // icon of the top/left task
- View showWindowsView = findViewById(R.id.show_windows);
- updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName));
- }
-
- /**
- * Returns a callback that updates the state of the filter and the recents overview
- *
- * @param taskPackageName package name of the task to filter by
- */
- @Nullable
- protected View.OnClickListener getFilterUpdateCallback(String taskPackageName) {
- View.OnClickListener cb = (view) -> {
- // update and apply a new filter
- getRecentsView().setAndApplyFilter(taskPackageName);
- };
-
- if (!getRecentsView().getFilterState().shouldShowFilterUI(taskPackageName)) {
- cb = null;
- }
- return cb;
- }
-
- /**
- * Sets the correct visibility and callback on the provided filterView based on whether
- * the callback is null or not
- */
- protected void updateFilterCallback(@NonNull View filterView,
- @Nullable View.OnClickListener callback) {
- // Filtering changes alpha instead of the visibility since visibility
- // can be altered separately through RecentsView#resetFromSplitSelectionState()
- if (callback == null) {
- filterView.setAlpha(0);
- } else {
- filterView.setAlpha(1);
- }
-
- filterView.setOnClickListener(callback);
- }
-
- public TaskIdAttributeContainer[] getTaskIdAttributeContainers() {
- return mTaskIdAttributeContainer;
- }
-
- @Nullable
- public Task getTask() {
- return mTask;
- }
-
- /**
- * Check if given {@code taskId} is tracked in this view
- */
- public boolean containsTaskId(int taskId) {
- return mTask != null && mTask.key.id == taskId;
- }
-
- /**
- * @return integer array of two elements to be size consistent with max number of tasks possible
- * index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value
- */
- public int[] getTaskIds() {
- return Arrays.copyOf(mTaskIdContainer, mTaskIdContainer.length);
- }
-
- public boolean containsMultipleTasks() {
- return mTaskIdContainer[1] != -1;
- }
-
- /**
- * Returns the TaskIdAttributeContainer corresponding to a given taskId, or null if the TaskView
- * does not contain a Task with that ID.
- */
- @Nullable
- public TaskIdAttributeContainer getTaskAttributesById(int taskId) {
- for (TaskIdAttributeContainer attributes : mTaskIdAttributeContainer) {
- if (attributes.getTask().key.id == taskId) {
- return attributes;
- }
- }
-
- return null;
- }
-
- public TaskThumbnailView getThumbnail() {
- return mSnapshotView;
- }
-
- void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
- if (mTask != null && thumbnailDatas != null) {
- final ThumbnailData thumbnailData = thumbnailDatas.get(mTask.key.id);
- if (thumbnailData != null) {
- mSnapshotView.setThumbnail(mTask, thumbnailData);
- return;
- }
- }
-
- mSnapshotView.refresh();
- }
-
- /** TODO(b/197033698) Remove all usages of above method and migrate to this one */
- public TaskThumbnailView[] getThumbnails() {
- return new TaskThumbnailView[]{mSnapshotView};
- }
-
- public TaskViewIcon getIconView() {
- return mIconView;
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null || getTask() == null) {
- return false;
- }
- SplitSelectStateController splitSelectStateController =
- recentsView.getSplitSelectController();
- // Disable taps for split selection animation unless we have multiple tasks
- boolean disableTapsForSplitSelect =
- splitSelectStateController.isSplitSelectActive()
- && splitSelectStateController.getInitialTaskId() == getTask().key.id
- && !containsMultipleTasks();
- if (disableTapsForSplitSelect) {
- return false;
- }
-
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mLastTouchDownPosition.set(ev.getX(), ev.getY());
- }
- return super.dispatchTouchEvent(ev);
- }
-
- /**
- * @return taskId that split selection was initiated with,
- * {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of
- * split selection
- */
- protected int getThisTaskCurrentlyInSplitSelection() {
- SplitSelectStateController splitSelectController =
- getRecentsView().getSplitSelectController();
- int initSplitTaskId = INVALID_TASK_ID;
- for (TaskIdAttributeContainer container : getTaskIdAttributeContainers()) {
- int taskId = container.getTask().key.id;
- if (taskId == splitSelectController.getInitialTaskId()) {
- initSplitTaskId = taskId;
- break;
- }
- }
- return initSplitTaskId;
- }
-
- private void onClick(View view) {
- if (getTask() == null) {
- return;
- }
- if (confirmSecondSplitSelectApp()) {
- return;
- }
- launchTasks();
- mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
- .log(LAUNCHER_TASK_LAUNCH_TAP);
- }
-
- /**
- * @return {@code true} if user is already in split select mode and this tap was to choose the
- * second app. {@code false} otherwise
- */
- protected boolean confirmSecondSplitSelectApp() {
- int index = getLastSelectedChildTaskIndex();
- TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
- if (container != null) {
- return getRecentsView().confirmSplitSelect(this, container.getTask(),
- container.getIconView().getDrawable(), container.getThumbnailView(),
- container.getThumbnailView().getThumbnail(), /* intent */ null,
- /* user */ null, container.getItemInfo());
- }
- return false;
- }
-
- /**
- * Returns the task index of the last selected child task (0 or 1).
- * If we contain multiple tasks and this TaskView is used as part of split selection, the
- * selected child task index will be that of the remaining task.
- */
- protected int getLastSelectedChildTaskIndex() {
- return 0;
- }
-
- /**
- * Starts the task associated with this view and animates the startup.
- * @return CompletionStage to indicate the animation completion or null if the launch failed.
- */
- @Nullable
- public RunnableList launchTaskAnimated() {
- if (mTask != null) {
- TestLogging.recordEvent(
- TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
- ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this, null);
- opts.options.setLaunchDisplayId(
- getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
- if (ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(mTask.key, opts.options)) {
- ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
- RecentsView recentsView = getRecentsView();
- if (recentsView.getRunningTaskViewId() != -1) {
- recentsView.onTaskLaunchedInLiveTileMode();
-
- // Return a fresh callback in the live tile case, so that it's not accidentally
- // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner.
- RunnableList callbackList = new RunnableList();
- recentsView.addSideTaskLaunchCallback(callbackList);
- return callbackList;
- }
- if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
- // If the recents transition is running (ie. in live tile mode), then the start
- // of a new task will merge into the existing transition and it currently will
- // not be run independently, so we need to rely on the onTaskAppeared() call
- // for the new task to trigger the side launch callback to flush this runnable
- // list (which is usually flushed when the app launch animation finishes)
- recentsView.addSideTaskLaunchCallback(opts.onEndCallback);
- }
- return opts.onEndCallback;
- } else {
- notifyTaskLaunchFailed(TAG);
- return null;
- }
- } else {
- return null;
- }
- }
-
- /**
- * Starts the task associated with this view without any animation
- */
- public void launchTask(@NonNull Consumer<Boolean> callback) {
- launchTask(callback, false /* isQuickswitch */);
- }
-
- /**
- * Starts the task associated with this view without any animation
- */
- public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
- if (mTask != null) {
- TestLogging.recordEvent(
- TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
-
- final TaskRemovedDuringLaunchListener
- failureListener = new TaskRemovedDuringLaunchListener();
- 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(mActivity, mTask.key.id, () -> {
- notifyTaskLaunchFailed(TAG);
- RecentsView rv = getRecentsView();
- if (rv != null) {
- // Disable animations for now, as it is an edge case and the app usually
- // covers launcher and also any state transition animation also gets
- // clobbered by QuickstepTransitionManager.createWallpaperOpenAnimations
- // when launcher shows again
- rv.startHome(false /* animated */);
- if (rv.mSizeStrategy.getTaskbarController() != null) {
- // LauncherTaskbarUIController depends on the launcher state when
- // checking whether to handle resume, but that can come in before
- // startHome() changes the state, so force-refresh here to ensure the
- // taskbar is updated
- rv.mSizeStrategy.getTaskbarController().refreshResumedState();
- }
- }
- });
- }
- // Indicate success once the system has indicated that the transition has started
- ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(getContext(), 0, 0,
- MAIN_EXECUTOR.getHandler(),
- elapsedRealTime -> {
- callback.accept(true);
- },
- elapsedRealTime -> {
- failureListener.onTransitionFinished();
- });
- opts.setLaunchDisplayId(
- getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
- if (isQuickswitch) {
- opts.setFreezeRecentTasksReordering();
- }
- opts.setDisableStartingWindow(mSnapshotView.shouldShowSplashView());
- Task.TaskKey key = mTask.key;
- UI_HELPER_EXECUTOR.execute(() -> {
- if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(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
- // above
- MAIN_EXECUTOR.post(() -> {
- notifyTaskLaunchFailed(TAG);
- callback.accept(false);
- });
- }
- });
- } else {
- callback.accept(false);
- }
- }
-
- /**
- * Launch of the current task (both live and inactive tasks) with an animation.
- */
- @Nullable
- public RunnableList launchTasks() {
- RecentsView recentsView = getRecentsView();
- RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
- if (isRunningTask() && remoteTargetHandles != null) {
- if (!mIsClickableAsLiveTile) {
- Log.e(TAG, "TaskView is not clickable as a live tile; returning to home.");
- return null;
- }
-
- mIsClickableAsLiveTile = false;
- RemoteAnimationTargets targets;
- if (remoteTargetHandles.length == 1) {
- targets = remoteTargetHandles[0].getTransformParams().getTargetSet();
- } else {
- RemoteAnimationTarget[] apps = Arrays.stream(remoteTargetHandles)
- .flatMap(handle -> Stream.of(
- handle.getTransformParams().getTargetSet().apps))
- .toArray(RemoteAnimationTarget[]::new);
- RemoteAnimationTarget[] wallpapers = Arrays.stream(remoteTargetHandles)
- .flatMap(handle -> Stream.of(
- handle.getTransformParams().getTargetSet().wallpapers))
- .toArray(RemoteAnimationTarget[]::new);
- targets = new RemoteAnimationTargets(apps, wallpapers,
- remoteTargetHandles[0].getTransformParams().getTargetSet().nonApps,
- remoteTargetHandles[0].getTransformParams().getTargetSet().targetMode);
- }
- if (targets == null) {
- // If the recents animation is cancelled somehow between the parent if block and
- // here, try to launch the task as a non live tile task.
- RunnableList runnableList = launchTaskAnimated();
- if (runnableList == null) {
- Log.e(TAG, "Recents animation cancelled and cannot launch task as non-live tile"
- + "; returning to home");
- }
- mIsClickableAsLiveTile = true;
- return runnableList;
- }
-
- RunnableList runnableList = new RunnableList();
- AnimatorSet anim = new AnimatorSet();
- TaskViewUtils.composeRecentsLaunchAnimator(
- anim, this, targets.apps,
- targets.wallpapers, targets.nonApps, true /* launcherClosing */,
- mActivity.getStateManager(), recentsView,
- recentsView.getDepthController());
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animator) {
- if (mTask != null && mTask.key.displayId != getRootViewDisplayId()) {
- launchTaskAnimated();
- }
- mIsClickableAsLiveTile = true;
- runEndCallback();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- runEndCallback();
- }
-
- private void runEndCallback() {
- runnableList.executeAllAndDestroy();
- }
- });
- anim.start();
- recentsView.onTaskLaunchedInLiveTileMode();
- return runnableList;
- } else {
- return launchTaskAnimated();
- }
- }
-
- /**
- * See {@link TaskDataChanges}
- * @param visible If this task view will be visible to the user in overview or hidden
- */
- public void onTaskListVisibilityChanged(boolean visible) {
- onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL);
- }
-
- /**
- * See {@link TaskDataChanges}
- * @param visible If this task view will be visible to the user in overview or hidden
- */
- public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) {
- if (mTask == null) {
- return;
- }
- cancelPendingLoadTasks();
- if (visible) {
- // These calls are no-ops if the data is already loaded, try and load the high
- // resolution thumbnail if the state permits
- RecentsModel model = RecentsModel.INSTANCE.get(getContext());
- TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
- TaskIconCache iconCache = model.getIconCache();
-
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
- mTask, thumbnail -> {
- mSnapshotView.setThumbnail(mTask, thumbnail);
- });
- }
- if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
- mIconLoadRequest = iconCache.updateIconInBackground(mTask,
- (task) -> {
- setIcon(mIconView, task.icon);
- if (enableOverviewIconMenu()) {
- setText(mIconView, task.title);
- }
- mDigitalWellBeingToast.initialize(task);
- });
- }
- if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) {
- mCurrentFullscreenParams.updateCornerRadius(getContext());
- }
- } else {
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- mSnapshotView.setThumbnail(null, null);
- // Reset the task thumbnail reference as well (it will be fetched from the cache or
- // reloaded next time we need it)
- mTask.thumbnail = null;
- }
- if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
- setIcon(mIconView, null);
- if (enableOverviewIconMenu()) {
- setText(mIconView, null);
- }
- }
- }
- }
-
- protected boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) {
- return (dataChange & flag) == flag;
- }
-
- protected void cancelPendingLoadTasks() {
- if (mThumbnailLoadRequest != null) {
- mThumbnailLoadRequest.cancel();
- mThumbnailLoadRequest = null;
- }
- if (mIconLoadRequest != null) {
- mIconLoadRequest.cancel();
- mIconLoadRequest = null;
- }
- }
-
- private boolean showTaskMenu(TaskViewIcon iconView) {
- if (!getRecentsView().canLaunchFullscreenTask()) {
- // Don't show menu when selecting second split screen app
- return true;
- }
-
- if (!mActivity.getDeviceProfile().isTablet
- && !getRecentsView().isClearAllHidden()) {
- getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
- return false;
- } else {
- mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
- .log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS);
- return showTaskMenuWithContainer(iconView);
- }
- }
-
- protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) {
- TaskIdAttributeContainer menuContainer =
- mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1];
- DeviceProfile dp = mActivity.getDeviceProfile();
- if (enableOverviewIconMenu() && iconView instanceof IconAppChipView) {
- ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ true);
- return TaskMenuView.showForTask(menuContainer,
- () -> ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ false));
- } else if (dp.isTablet) {
- int alignedOptionIndex = 0;
- if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) {
- if (Flags.enableGridOnlyOverview()) {
- // With no focused task, there is less available space below the tasks, so align
- // the arrow to the third option in the menu.
- alignedOptionIndex = 2;
- } else {
- // Bottom row of landscape grid aligns arrow to second option to avoid clipping
- alignedOptionIndex = 1;
- }
- }
- return TaskMenuViewWithArrow.Companion.showForTask(menuContainer,
- alignedOptionIndex);
- } else {
- return TaskMenuView.showForTask(menuContainer);
- }
- }
-
- protected void setIcon(TaskViewIcon iconView, @Nullable Drawable icon) {
- if (icon != null) {
- iconView.setDrawable(icon);
- iconView.setOnClickListener(v -> {
- if (confirmSecondSplitSelectApp()) {
- return;
- }
- showTaskMenu(iconView);
- });
- iconView.setOnLongClickListener(v -> {
- requestDisallowInterceptTouchEvent(true);
- return showTaskMenu(iconView);
- });
- } else {
- iconView.setDrawable(null);
- iconView.setOnClickListener(null);
- iconView.setOnLongClickListener(null);
- }
- }
-
- protected void setText(TaskViewIcon iconView, CharSequence text) {
- iconView.setText(text);
- }
-
- public void setOrientationState(RecentsOrientedState orientationState) {
- mIconView.setIconOrientation(orientationState, isGridTask());
- setThumbnailOrientation(orientationState);
- }
-
- protected void setThumbnailOrientation(RecentsOrientedState orientationState) {
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
-
- // TODO(b/271468547), we should default to setting trasnlations only on the snapshot instead
- // of a hybrid of both margins and translations
- LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
- snapshotParams.topMargin = thumbnailTopMargin;
- mSnapshotView.setLayoutParams(snapshotParams);
-
- mSnapshotView.getTaskOverlay().updateOrientationState(orientationState);
- mDigitalWellBeingToast.initialize(mTask);
- }
-
- /**
- * Returns whether the task is part of overview grid and not being focused.
- */
- public boolean isGridTask() {
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- return deviceProfile.isTablet && !isFocusedTask();
- }
-
- /** Whether this task view represents the desktop */
- public boolean isDesktopTask() {
- return false;
- }
-
- /**
- * Called to animate a smooth transition when going directly from an app into Overview (and
- * vice versa). Icons fade in, and DWB banners slide in with a "shift up" animation.
- */
- protected void setIconsAndBannersTransitionProgress(float progress, boolean invert) {
- if (invert) {
- progress = 1 - progress;
- }
- mFocusTransitionProgress = progress;
- float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION;
- float lowerClamp = invert ? 1f - iconScalePercentage : 0;
- float upperClamp = invert ? 1 : iconScalePercentage;
- float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, lowerClamp, upperClamp)
- .getInterpolation(progress);
- mIconView.setContentAlpha(scale);
- mDigitalWellBeingToast.updateBannerOffset(1f - scale);
- }
-
- public void setIconScaleAnimStartProgress(float startProgress) {
- mIconScaleAnimStartProgress = startProgress;
- }
-
- public void animateIconScaleAndDimIntoView() {
- if (mIconAndDimAnimator != null) {
- mIconAndDimAnimator.cancel();
- }
- mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1);
- mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress);
- mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR);
- mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mIconAndDimAnimator = null;
- }
- });
- mIconAndDimAnimator.start();
- }
-
- protected void setIconScaleAndDim(float iconScale) {
- setIconScaleAndDim(iconScale, false);
- }
-
- private void setIconScaleAndDim(float iconScale, boolean invert) {
- if (mIconAndDimAnimator != null) {
- mIconAndDimAnimator.cancel();
- }
- setIconsAndBannersTransitionProgress(iconScale, invert);
- }
-
- protected void resetPersistentViewTransforms() {
- mNonGridTranslationX = mGridTranslationX =
- mGridTranslationY = mBoxTranslationY = mNonGridPivotTranslationX = 0f;
- resetViewTransforms();
- }
-
- protected void resetViewTransforms() {
- // fullscreenTranslation and accumulatedTranslation should not be reset, as
- // resetViewTransforms is called during Quickswitch scrolling.
- mDismissTranslationX = mTaskOffsetTranslationX =
- mTaskResistanceTranslationX = mSplitSelectTranslationX = mGridEndTranslationX = 0f;
- mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY = 0f;
- if (getRecentsView() == null || !getRecentsView().isSplitSelectionActive()) {
- mSplitSelectTranslationY = 0f;
- }
-
- setSnapshotScale(1f);
- applyTranslationX();
- applyTranslationY();
- setTranslationZ(0);
- setAlpha(mStableAlpha);
- setIconScaleAndDim(1);
- setColorTint(0, 0);
- mSnapshotView.resetViewTransforms();
- }
-
- public void setStableAlpha(float parentAlpha) {
- mStableAlpha = parentAlpha;
- setAlpha(mStableAlpha);
- }
-
- @Override
- public void onRecycle() {
- resetPersistentViewTransforms();
- // Clear any references to the thumbnail (it will be re-read either from the cache or the
- // system on next bind)
- mSnapshotView.setThumbnail(mTask, null);
- setOverlayEnabled(false);
- onTaskListVisibilityChanged(false);
- mBorderEnabled = false;
- }
-
- public float getTaskCornerRadius() {
- return mCurrentFullscreenParams.mCornerRadius;
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mActivity.getDeviceProfile().isTablet) {
- setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : right - left);
- setPivotY(mSnapshotView.getTop());
- } else {
- setPivotX((right - left) * 0.5f);
- setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f);
- }
- SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight());
- setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT);
- }
-
- /**
- * How much to scale down pages near the edge of the screen.
- */
- public static float getEdgeScaleDownFactor(DeviceProfile deviceProfile) {
- return deviceProfile.isTablet ? EDGE_SCALE_DOWN_FACTOR_GRID
- : EDGE_SCALE_DOWN_FACTOR_CAROUSEL;
- }
-
- private void setNonGridScale(float nonGridScale) {
- mNonGridScale = nonGridScale;
- applyScale();
- }
-
- public float getNonGridScale() {
- return mNonGridScale;
- }
-
- private void setSnapshotScale(float dismissScale) {
- mDismissScale = dismissScale;
- applyScale();
- }
-
- /**
- * Moves TaskView between carousel and 2 row grid.
- *
- * @param gridProgress 0 = carousel; 1 = 2 row grid.
- */
- public void setGridProgress(float gridProgress) {
- mGridProgress = gridProgress;
- applyTranslationX();
- applyTranslationY();
- applyScale();
- }
-
- private void applyScale() {
- float scale = 1;
- scale *= getPersistentScale();
- scale *= mDismissScale;
- setScaleX(scale);
- setScaleY(scale);
- updateSnapshotRadius();
- }
-
- /**
- * Returns multiplication of scale that is persistent (e.g. fullscreen and grid), and does not
- * change according to a temporary state.
- */
- public float getPersistentScale() {
- float scale = 1;
- scale *= Utilities.mapRange(mGridProgress, mNonGridScale, 1f);
- return scale;
- }
-
- /**
- * Updates alpha of task thumbnail splash on swipe up/down.
- */
- public void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
- mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha;
- applyThumbnailSplashAlpha();
- }
-
- protected void applyThumbnailSplashAlpha() {
- mSnapshotView.setSplashAlpha(mTaskThumbnailSplashAlpha);
- }
-
- protected void refreshTaskThumbnailSplash() {
- mSnapshotView.refreshSplashView();
- }
-
- private void setSplitSelectTranslationX(float x) {
- mSplitSelectTranslationX = x;
- applyTranslationX();
- }
-
- private void setSplitSelectTranslationY(float y) {
- mSplitSelectTranslationY = y;
- applyTranslationY();
- }
-
- private void setDismissTranslationX(float x) {
- mDismissTranslationX = x;
- applyTranslationX();
- }
-
- private void setDismissTranslationY(float y) {
- mDismissTranslationY = y;
- applyTranslationY();
- }
-
- private void setTaskOffsetTranslationX(float x) {
- mTaskOffsetTranslationX = x;
- applyTranslationX();
- }
-
- private void setTaskOffsetTranslationY(float y) {
- mTaskOffsetTranslationY = y;
- applyTranslationY();
- }
-
- private void setTaskResistanceTranslationX(float x) {
- mTaskResistanceTranslationX = x;
- applyTranslationX();
- }
-
- private void setTaskResistanceTranslationY(float y) {
- mTaskResistanceTranslationY = y;
- applyTranslationY();
- }
-
- public float getNonGridTranslationX() {
- return mNonGridTranslationX;
- }
-
- /**
- * Updates X coordinate of non-grid translation.
- */
- public void setNonGridTranslationX(float nonGridTranslationX) {
- mNonGridTranslationX = nonGridTranslationX;
- applyTranslationX();
- }
-
- public void setGridTranslationX(float gridTranslationX) {
- mGridTranslationX = gridTranslationX;
- applyTranslationX();
- }
-
- public float getGridTranslationX() {
- return mGridTranslationX;
- }
-
- public void setGridTranslationY(float gridTranslationY) {
- mGridTranslationY = gridTranslationY;
- applyTranslationY();
- }
-
- public float getGridTranslationY() {
- return mGridTranslationY;
- }
-
- private void setGridEndTranslationX(float gridEndTranslationX) {
- mGridEndTranslationX = gridEndTranslationX;
- applyTranslationX();
- }
-
- /**
- * Set translation X for non-grid pivot
- */
- public void setNonGridPivotTranslationX(float nonGridPivotTranslationX) {
- mNonGridPivotTranslationX = nonGridPivotTranslationX;
- applyTranslationX();
- }
-
- public float getScrollAdjustment(boolean gridEnabled) {
- float scrollAdjustment = 0;
- if (gridEnabled) {
- scrollAdjustment += mGridTranslationX;
- } else {
- scrollAdjustment += getNonGridTranslationX();
- }
- return scrollAdjustment;
- }
-
- public float getOffsetAdjustment(boolean gridEnabled) {
- return getScrollAdjustment(gridEnabled);
- }
-
- public float getSizeAdjustment(boolean fullscreenEnabled) {
- float sizeAdjustment = 1;
- if (fullscreenEnabled) {
- sizeAdjustment *= mNonGridScale;
- }
- return sizeAdjustment;
- }
-
- private void setBoxTranslationY(float boxTranslationY) {
- mBoxTranslationY = boxTranslationY;
- applyTranslationY();
- }
-
- private void applyTranslationX() {
- setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
- + mSplitSelectTranslationX + mGridEndTranslationX + getPersistentTranslationX());
- }
-
- private void applyTranslationY() {
- setTranslationY(mDismissTranslationY + mTaskOffsetTranslationY + mTaskResistanceTranslationY
- + mSplitSelectTranslationY + getPersistentTranslationY());
- }
-
- /**
- * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does not
- * change according to a temporary state (e.g. task offset).
- */
- public float getPersistentTranslationX() {
- return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX)
- + getNonGridTrans(mNonGridPivotTranslationX);
- }
-
- /**
- * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does not
- * change according to a temporary state (e.g. task offset).
- */
- public float getPersistentTranslationY() {
- return mBoxTranslationY + getGridTrans(mGridTranslationY);
- }
-
- public FloatProperty<TaskView> getPrimarySplitTranslationProperty() {
- return getPagedOrientationHandler().getPrimaryValue(
- SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getSecondarySplitTranslationProperty() {
- return getPagedOrientationHandler().getSecondaryValue(
- SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() {
- return getPagedOrientationHandler().getPrimaryValue(
- DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getSecondaryDismissTranslationProperty() {
- return getPagedOrientationHandler().getSecondaryValue(
- DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getPrimaryTaskOffsetTranslationProperty() {
- return getPagedOrientationHandler().getPrimaryValue(
- TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getSecondaryTaskOffsetTranslationProperty() {
- return getPagedOrientationHandler().getSecondaryValue(
- TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getTaskResistanceTranslationProperty() {
- return getPagedOrientationHandler().getSecondaryValue(
- TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y);
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- // TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
- return false;
- }
-
- public boolean isEndQuickswitchCuj() {
- return mEndQuickswitchCuj;
- }
-
- public void setEndQuickswitchCuj(boolean endQuickswitchCuj) {
- mEndQuickswitchCuj = endQuickswitchCuj;
- }
-
- private int getExpectedViewHeight(View view) {
- int expectedHeight;
- int h = view.getLayoutParams().height;
- if (h > 0) {
- expectedHeight = h;
- } else {
- int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
- view.measure(m, m);
- expectedHeight = view.getMeasuredHeight();
- }
- return expectedHeight;
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
-
- info.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close,
- getContext().getText(R.string.accessibility_close)));
-
- final Context context = getContext();
- for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
- if (taskContainer == null) {
- continue;
- }
- for (SystemShortcut s : TraceHelper.allowIpcs(
- "TV.a11yInfo", () -> getEnabledShortcuts(this, taskContainer))) {
- info.addAction(s.createAccessibilityAction(context));
- }
- }
-
- if (mDigitalWellBeingToast.hasLimit()) {
- info.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(
- R.string.accessibility_app_usage_settings,
- getContext().getText(R.string.accessibility_app_usage_settings)));
- }
-
- final RecentsView recentsView = getRecentsView();
- final AccessibilityNodeInfo.CollectionItemInfo itemInfo =
- AccessibilityNodeInfo.CollectionItemInfo.obtain(
- 0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1,
- 1, false);
- info.setCollectionItemInfo(itemInfo);
- }
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (action == R.string.accessibility_close) {
- getRecentsView().dismissTask(this, true /*animateTaskView*/,
- true /*removeTask*/);
- return true;
- }
-
- if (action == R.string.accessibility_app_usage_settings) {
- mDigitalWellBeingToast.openAppUsageSettings(this);
- return true;
- }
-
- for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
- if (taskContainer == null) {
- continue;
- }
- for (SystemShortcut s : getEnabledShortcuts(this,
- taskContainer)) {
- if (s.hasHandlerForAction(action)) {
- s.onClick(this);
- return true;
- }
- }
- }
-
- return super.performAccessibilityAction(action, arguments);
- }
-
- @Nullable
- public RecentsView getRecentsView() {
- return (RecentsView) getParent();
- }
-
- RecentsPagedOrientationHandler getPagedOrientationHandler() {
- return getRecentsView().mOrientationState.getOrientationHandler();
- }
-
- private void notifyTaskLaunchFailed(String tag) {
- String msg = "Failed to launch task";
- if (mTask != null) {
- msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")";
- }
- Log.w(tag, msg);
- Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show();
- }
-
- /**
- * Hides the icon and shows insets when this TaskView is about to be shown fullscreen.
- *
- * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
- */
- public void setFullscreenProgress(float progress) {
- progress = Utilities.boundToRange(progress, 0, 1);
- mFullscreenProgress = progress;
- mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
- mSnapshotView.getTaskOverlay().setFullscreenProgress(progress);
-
- // Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are
- // oversized and banner would look disproportionately large.
- if (mActivity.getStateManager().getState() != BACKGROUND_APP) {
- setIconsAndBannersTransitionProgress(progress, true);
- }
-
- updateSnapshotRadius();
- }
-
- protected void updateSnapshotRadius() {
- updateCurrentFullscreenParams();
- mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
- }
-
- void updateCurrentFullscreenParams() {
- updateFullscreenParams(mCurrentFullscreenParams);
- }
-
- protected void updateFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
- if (getRecentsView() == null) {
- return;
- }
- fullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
- getScaleX());
- }
-
- /**
- * Updates TaskView scaling and translation required to support variable width if enabled, while
- * ensuring TaskView fits into screen in fullscreen.
- */
- void updateTaskSize() {
- ViewGroup.LayoutParams params = getLayoutParams();
- float nonGridScale;
- float boxTranslationY;
- int expectedWidth;
- int expectedHeight;
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- final int thumbnailPadding = deviceProfile.overviewTaskThumbnailTopMarginPx;
- final Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize();
- final int taskWidth = lastComputedTaskSize.width();
- final int taskHeight = lastComputedTaskSize.height();
- if (deviceProfile.isTablet) {
- int boxWidth;
- int boxHeight;
- boolean isFocusedTask = isFocusedTask();
- if (isDesktopTask()) {
- Rect lastComputedDesktopTaskSize =
- getRecentsView().getLastComputedDesktopTaskSize();
- boxWidth = lastComputedDesktopTaskSize.width();
- boxHeight = lastComputedDesktopTaskSize.height();
- } else if (isFocusedTask) {
- // Task will be focused and should use focused task size. Use focusTaskRatio
- // that is associated with the original orientation of the focused task.
- boxWidth = taskWidth;
- boxHeight = taskHeight;
- } else {
- // Otherwise task is in grid, and should use lastComputedGridTaskSize.
- Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize();
- boxWidth = lastComputedGridTaskSize.width();
- boxHeight = lastComputedGridTaskSize.height();
- }
-
- // Bound width/height to the box size.
- expectedWidth = boxWidth;
- expectedHeight = boxHeight + thumbnailPadding;
-
- // Scale to to fit task Rect.
- if (enableGridOnlyOverview()) {
- final Rect lastComputedCarouselTaskSize =
- getRecentsView().getLastComputedCarouselTaskSize();
- nonGridScale = lastComputedCarouselTaskSize.width() / (float) taskWidth;
- } else {
- nonGridScale = taskWidth / (float) boxWidth;
- }
-
- // Align to top of task Rect.
- boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f;
- } else {
- nonGridScale = 1f;
- boxTranslationY = 0f;
- expectedWidth = enableOverviewIconMenu() ? taskWidth : LayoutParams.MATCH_PARENT;
- expectedHeight = enableOverviewIconMenu()
- ? taskHeight + thumbnailPadding
- : LayoutParams.MATCH_PARENT;
- }
-
- setNonGridScale(nonGridScale);
- setBoxTranslationY(boxTranslationY);
- if (params.width != expectedWidth || params.height != expectedHeight) {
- params.width = expectedWidth;
- params.height = expectedHeight;
- setLayoutParams(params);
- }
- }
-
- private float getGridTrans(float endTranslation) {
- return Utilities.mapRange(mGridProgress, 0, endTranslation);
- }
-
- private float getNonGridTrans(float endTranslation) {
- return endTranslation - getGridTrans(endTranslation);
- }
-
- public boolean isRunningTask() {
- if (getRecentsView() == null) {
- return false;
- }
- return this == getRecentsView().getRunningTaskView();
- }
-
- public boolean isFocusedTask() {
- if (getRecentsView() == null) {
- return false;
- }
- return this == getRecentsView().getFocusedTaskView();
- }
-
- public void setShowScreenshot(boolean showScreenshot) {
- mShowScreenshot = showScreenshot;
- }
-
- public boolean showScreenshot() {
- if (!isRunningTask()) {
- return true;
- }
- return mShowScreenshot;
- }
-
- public void setOverlayEnabled(boolean overlayEnabled) {
- mSnapshotView.setOverlayEnabled(overlayEnabled);
- }
-
- public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
- getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition,
- getLogEventForPosition(splitPositionOption.stagePosition));
- }
-
- /**
- * Set a color tint on the snapshot and supporting views.
- */
- public void setColorTint(float amount, int tintColor) {
- mSnapshotView.setDimAlpha(amount);
- mIconView.setIconColorTint(tintColor, amount);
- mDigitalWellBeingToast.setBannerColorTint(tintColor, amount);
- }
-
-
- private int getRootViewDisplayId() {
- Display display = getRootView().getDisplay();
- return display != null ? display.getDisplayId() : DEFAULT_DISPLAY;
- }
-
- /**
- * Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
- * IconView is unaffected.
- *
- * @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value
- */
- void setThumbnailVisibility(int visibility, int taskId) {
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (child != mIconView) {
- child.setVisibility(visibility);
- }
- }
- }
-
- /**
- * We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
- */
- public static class FullscreenDrawParams {
-
- private float mCornerRadius;
- private float mWindowCornerRadius;
-
- public float mCurrentDrawnCornerRadius;
-
- public FullscreenDrawParams(Context context) {
- updateCornerRadius(context);
- }
-
- /** Recomputes the start and end corner radius for the given Context. */
- public void updateCornerRadius(Context context) {
- mCornerRadius = computeTaskCornerRadius(context);
- mWindowCornerRadius = computeWindowCornerRadius(context);
- }
-
- @VisibleForTesting
- public float computeTaskCornerRadius(Context context) {
- return TaskCornerRadius.get(context);
- }
-
- @VisibleForTesting
- public float computeWindowCornerRadius(Context context) {
- return QuickStepContract.getWindowCornerRadius(context);
- }
-
- /**
- * Sets the progress in range [0, 1]
- */
- public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale) {
- mCurrentDrawnCornerRadius =
- Utilities.mapRange(fullscreenProgress, mCornerRadius, mWindowCornerRadius)
- / parentScale / taskViewScale;
- }
- }
-
- public class TaskIdAttributeContainer {
- private final TaskThumbnailView mThumbnailView;
- private final Task mTask;
- private final TaskViewIcon mIconView;
- /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
- private @SplitConfigurationOptions.StagePosition int mStagePosition;
- @IdRes
- private final int mA11yNodeId;
-
- public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView,
- TaskViewIcon iconView, int stagePosition) {
- this.mTask = task;
- this.mThumbnailView = thumbnailView;
- this.mIconView = iconView;
- this.mStagePosition = stagePosition;
- this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ?
- R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo;
- }
-
- public TaskThumbnailView getThumbnailView() {
- return mThumbnailView;
- }
-
- public Task getTask() {
- return mTask;
- }
-
- public WorkspaceItemInfo getItemInfo() {
- return TaskView.this.getItemInfo(mTask);
- }
-
- public TaskView getTaskView() {
- return TaskView.this;
- }
-
- public TaskViewIcon getIconView() {
- return mIconView;
- }
-
- public int getStagePosition() {
- return mStagePosition;
- }
-
- void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) {
- this.mStagePosition = stagePosition;
- }
-
- public int getA11yNodeId() {
- return mA11yNodeId;
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
new file mode 100644
index 0000000..94ebc5a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -0,0 +1,1796 @@
+/*
+ * Copyright (C) 2017 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.views
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.annotation.IdRes
+import android.app.ActivityOptions
+import android.content.Context
+import android.content.Intent
+import android.graphics.Canvas
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.util.AttributeSet
+import android.util.FloatProperty
+import android.util.Log
+import android.view.Display
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.view.ViewStub
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import android.widget.FrameLayout
+import android.widget.Toast
+import androidx.annotation.IntDef
+import androidx.annotation.VisibleForTesting
+import androidx.core.view.updateLayoutParams
+import com.android.app.animation.Interpolators
+import com.android.launcher3.Flags.enableCursorHoverStates
+import com.android.launcher3.Flags.enableFocusOutline
+import com.android.launcher3.Flags.enableGridOnlyOverview
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.Flags.privateSpaceRestrictAccessibilityDrag
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.anim.AnimatedFloat
+import com.android.launcher3.config.FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.ItemInfoWithIcon
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.testing.TestLogging
+import com.android.launcher3.testing.shared.TestProtocol
+import com.android.launcher3.util.CancellableTask
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
+import com.android.launcher3.util.RunnableList
+import com.android.launcher3.util.SafeCloseable
+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.rects.set
+import com.android.launcher3.views.ActivityContext
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.RemoteAnimationTargets
+import com.android.quickstep.TaskAnimationManager
+import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.TaskOverlayFactory.TaskOverlay
+import com.android.quickstep.TaskUtils
+import com.android.quickstep.TaskViewUtils
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler
+import com.android.quickstep.task.thumbnail.TaskThumbnail
+import com.android.quickstep.task.thumbnail.TaskThumbnailView
+import com.android.quickstep.task.viewmodel.TaskViewData
+import com.android.quickstep.util.ActiveGestureErrorDetector
+import com.android.quickstep.util.ActiveGestureLog
+import com.android.quickstep.util.BorderAnimator
+import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
+import com.android.quickstep.util.RecentsOrientedState
+import com.android.quickstep.util.TaskCornerRadius
+import com.android.quickstep.util.TaskRemovedDuringLaunchListener
+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 com.android.systemui.shared.system.QuickStepContract
+
+/** A task in the Recents view. */
+open class TaskView
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0,
+ focusBorderAnimator: BorderAnimator? = null,
+ hoverBorderAnimator: BorderAnimator? = null
+) : FrameLayout(context, attrs), ViewPool.Reusable {
+ /**
+ * Used in conjunction with [onTaskListVisibilityChanged], providing more granularity on which
+ * components of this task require an update
+ */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS)
+ annotation class TaskDataChanges
+
+ /** Type of task view */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(Type.SINGLE, Type.GROUPED, Type.DESKTOP)
+ annotation class Type {
+ companion object {
+ const val SINGLE = 1
+ const val GROUPED = 2
+ const val DESKTOP = 3
+ }
+ }
+
+ val taskViewData = TaskViewData()
+ val taskIds: IntArray
+ /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
+ get() = taskContainers.map { it.task.key.id }.toIntArray()
+
+ val thumbnailViews: Array<TaskThumbnailViewDeprecated>
+ get() = taskContainers.map { it.thumbnailViewDeprecated }.toTypedArray()
+
+ val isGridTask: Boolean
+ /** Returns whether the task is part of overview grid and not being focused. */
+ get() = container.deviceProfile.isTablet && !isFocusedTask
+
+ val isRunningTask: Boolean
+ get() = this === recentsView?.runningTaskView
+
+ val isFocusedTask: Boolean
+ get() = this === recentsView?.focusedTaskView
+
+ val taskCornerRadius: Float
+ get() = currentFullscreenParams.cornerRadius
+
+ val recentsView: RecentsView<*, *>?
+ get() = parent as? RecentsView<*, *>
+
+ 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
+
+ @get:Deprecated("Use [taskContainers] instead.")
+ val firstThumbnailViewDeprecated: TaskThumbnailViewDeprecated
+ /** Returns the first thumbnailView of the TaskView. */
+ get() = taskContainers[0].thumbnailViewDeprecated
+
+ @get:Deprecated("Use [taskContainers] instead.")
+ val firstItemInfo: ItemInfo
+ get() = taskContainers[0].itemInfo
+
+ private val currentFullscreenParams = FullscreenDrawParams(context)
+ protected val container: RecentsViewContainer =
+ RecentsViewContainer.containerFromContext(context)
+ protected val lastTouchDownPosition = PointF()
+
+ // Derived view properties
+ protected val persistentScale: Float
+ /**
+ * Returns multiplication of scale that is persistent (e.g. fullscreen and grid), and does
+ * not change according to a temporary state.
+ */
+ get() = Utilities.mapRange(gridProgress, nonGridScale, 1f)
+
+ protected val persistentTranslationX: Float
+ /**
+ * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does
+ * not change according to a temporary state (e.g. task offset).
+ */
+ get() =
+ (getNonGridTrans(nonGridTranslationX) +
+ getGridTrans(this.gridTranslationX) +
+ getNonGridTrans(nonGridPivotTranslationX))
+
+ protected val persistentTranslationY: Float
+ /**
+ * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does
+ * not change according to a temporary state (e.g. task offset).
+ */
+ get() = boxTranslationY + getGridTrans(gridTranslationY)
+
+ protected val primarySplitTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getPrimaryValue(
+ SPLIT_SELECT_TRANSLATION_X,
+ SPLIT_SELECT_TRANSLATION_Y
+ )
+
+ protected val secondarySplitTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getSecondaryValue(
+ SPLIT_SELECT_TRANSLATION_X,
+ SPLIT_SELECT_TRANSLATION_Y
+ )
+
+ protected val primaryDismissTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getPrimaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
+
+ protected val secondaryDismissTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getSecondaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
+
+ protected val primaryTaskOffsetTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getPrimaryValue(
+ TASK_OFFSET_TRANSLATION_X,
+ TASK_OFFSET_TRANSLATION_Y
+ )
+
+ protected val secondaryTaskOffsetTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getSecondaryValue(
+ TASK_OFFSET_TRANSLATION_X,
+ TASK_OFFSET_TRANSLATION_Y
+ )
+
+ protected val taskResistanceTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getSecondaryValue(
+ TASK_RESISTANCE_TRANSLATION_X,
+ TASK_RESISTANCE_TRANSLATION_Y
+ )
+
+ private val tempCoordinates = FloatArray(2)
+ private val focusBorderAnimator: BorderAnimator?
+ private val hoverBorderAnimator: BorderAnimator?
+ private val rootViewDisplayId: Int
+ get() = rootView.display?.displayId ?: Display.DEFAULT_DISPLAY
+
+ /** Returns a list of all TaskContainers in the TaskView. */
+ lateinit var taskContainers: List<TaskContainer>
+ protected set
+
+ lateinit var orientedState: RecentsOrientedState
+
+ var taskViewId = UNBOUND_TASK_VIEW_ID
+ var isEndQuickSwitchCuj = false
+
+ // Various animation progress variables.
+ // progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
+ protected var fullscreenProgress = 0f
+ set(value) {
+ field = Utilities.boundToRange(value, 0f, 1f)
+ onFullscreenProgressChanged(field)
+ }
+
+ // gridProgress 0 = carousel; 1 = 2 row grid.
+ protected var gridProgress = 0f
+ set(value) {
+ field = value
+ onGridProgressChanged()
+ }
+
+ /**
+ * The modalness of this view is how it should be displayed when it is shown on its own in the
+ * modal state of overview. 0 being in context with other tasks, 1 being shown on its own.
+ */
+ protected var modalness = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ onModalnessUpdated(field)
+ }
+
+ protected var taskThumbnailSplashAlpha = 0f
+ set(value) {
+ field = value
+ applyThumbnailSplashAlpha()
+ }
+
+ protected var nonGridScale = 1f
+ set(value) {
+ field = value
+ applyScale()
+ }
+
+ private var dismissScale = 1f
+ set(value) {
+ field = value
+ applyScale()
+ }
+
+ private var dismissTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+
+ private var dismissTranslationY = 0f
+ set(value) {
+ field = value
+ applyTranslationY()
+ }
+
+ private var taskOffsetTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+
+ private var taskOffsetTranslationY = 0f
+ set(value) {
+ field = value
+ applyTranslationY()
+ }
+
+ private var taskResistanceTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+
+ private var taskResistanceTranslationY = 0f
+ set(value) {
+ field = value
+ applyTranslationY()
+ }
+
+ // The following translation variables should only be used in the same orientation as Launcher.
+ private var boxTranslationY = 0f
+ set(value) {
+ field = value
+ applyTranslationY()
+ }
+
+ // The following grid translations scales with mGridProgress.
+ protected var gridTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+
+ var gridTranslationY = 0f
+ protected set(value) {
+ field = value
+ applyTranslationY()
+ }
+
+ // The following grid translation is used to animate closing the gap between grid and clear all.
+ private var gridEndTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+
+ // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
+ // switch.
+ protected var nonGridTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+
+ protected var nonGridPivotTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+
+ // Used when in SplitScreenSelectState
+ private var splitSelectTranslationY = 0f
+ set(value) {
+ field = value
+ applyTranslationY()
+ }
+
+ private var splitSelectTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+
+ protected var stableAlpha = 1f
+ set(value) {
+ field = value
+ alpha = stableAlpha
+ }
+
+ protected var shouldShowScreenshot = false
+ get() = !isRunningTask || field
+
+ /** Enable or disable showing border on hover and focus change */
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ var borderEnabled = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ // Set the animation correctly in case it misses the hover/focus event during state
+ // transition
+ hoverBorderAnimator?.setBorderVisibility(visible = field && isHovered, animated = true)
+ focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
+ }
+
+ private var focusTransitionProgress = 1f
+ set(value) {
+ field = value
+ onFocusTransitionProgressUpdated(field)
+ }
+
+ private val focusTransitionPropertyFactory =
+ MultiPropertyFactory(
+ this,
+ FOCUS_TRANSITION,
+ FOCUS_TRANSITION_INDEX_COUNT,
+ { x: Float, y: Float -> x * y },
+ 1f
+ )
+ private val focusTransitionFullscreen =
+ focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_FULLSCREEN)
+ private val focusTransitionScaleAndDim =
+ focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_SCALE_AND_DIM)
+
+ /**
+ * Returns an animator of [focusTransitionScaleAndDim] that transition out with a built-in
+ * interpolator.
+ */
+ fun getFocusTransitionScaleAndDimOutAnimator(): ObjectAnimator =
+ AnimatedFloat { v ->
+ focusTransitionScaleAndDim.value =
+ FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+ }
+ .animateToValue(1f, 0f)
+
+ private var iconAndDimAnimator: ObjectAnimator? = null
+ // The current background requests to load the task thumbnail and icon
+ private val pendingThumbnailLoadRequests = mutableListOf<CancellableTask<*>>()
+ private val pendingIconLoadRequests = mutableListOf<CancellableTask<*>>()
+ private var isClickableAsLiveTile = true
+
+ init {
+ setOnClickListener { _ -> onClick() }
+ val keyboardFocusHighlightEnabled =
+ (ENABLE_KEYBOARD_QUICK_SWITCH.get() || enableFocusOutline())
+ val cursorHoverStatesEnabled = enableCursorHoverStates()
+ setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled)
+ context.obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes).use {
+ this.focusBorderAnimator =
+ focusBorderAnimator
+ ?: if (keyboardFocusHighlightEnabled)
+ createSimpleBorderAnimator(
+ currentFullscreenParams.cornerRadius.toInt(),
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_border_width
+ ),
+ { bounds: Rect -> getThumbnailBounds(bounds) },
+ this,
+ it.getColor(
+ R.styleable.TaskView_focusBorderColor,
+ BorderAnimator.DEFAULT_BORDER_COLOR
+ )
+ )
+ else null
+ this.hoverBorderAnimator =
+ hoverBorderAnimator
+ ?: if (cursorHoverStatesEnabled)
+ createSimpleBorderAnimator(
+ currentFullscreenParams.cornerRadius.toInt(),
+ context.resources.getDimensionPixelSize(
+ R.dimen.task_hover_border_width
+ ),
+ { bounds: Rect -> getThumbnailBounds(bounds) },
+ this,
+ it.getColor(
+ R.styleable.TaskView_hoverBorderColor,
+ BorderAnimator.DEFAULT_BORDER_COLOR
+ )
+ )
+ else null
+ }
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public override fun onFocusChanged(
+ gainFocus: Boolean,
+ direction: Int,
+ previouslyFocusedRect: Rect?
+ ) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
+ if (borderEnabled) {
+ focusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
+ }
+ }
+
+ override fun onHoverEvent(event: MotionEvent): Boolean {
+ if (borderEnabled) {
+ when (event.action) {
+ MotionEvent.ACTION_HOVER_ENTER ->
+ hoverBorderAnimator?.setBorderVisibility(visible = true, animated = true)
+ MotionEvent.ACTION_HOVER_EXIT ->
+ hoverBorderAnimator?.setBorderVisibility(visible = false, animated = true)
+ else -> {}
+ }
+ }
+ return super.onHoverEvent(event)
+ }
+
+ // avoid triggering hover event on child elements which would cause HOVER_EXIT for this
+ // task view
+ override fun onInterceptHoverEvent(event: MotionEvent) =
+ if (enableCursorHoverStates()) true else super.onInterceptHoverEvent(event)
+
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ val recentsView = recentsView ?: return false
+ val splitSelectStateController = recentsView.splitSelectController
+ // Disable taps for split selection animation unless we have a task not being selected
+ if (
+ splitSelectStateController.isSplitSelectActive &&
+ taskContainers.none { it.task.key.id != splitSelectStateController.initialTaskId }
+ ) {
+ return false
+ }
+ if (ev.action == MotionEvent.ACTION_DOWN) {
+ with(lastTouchDownPosition) {
+ x = ev.x
+ y = ev.y
+ }
+ }
+ return super.dispatchTouchEvent(ev)
+ }
+
+ override fun draw(canvas: Canvas) {
+ // Draw border first so any child views outside of the thumbnail bounds are drawn above it.
+ focusBorderAnimator?.drawBorder(canvas)
+ hoverBorderAnimator?.drawBorder(canvas)
+ super.draw(canvas)
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ val thumbnailTopMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+ if (container.deviceProfile.isTablet) {
+ pivotX = (if (layoutDirection == LAYOUT_DIRECTION_RTL) 0 else right - left).toFloat()
+ pivotY = thumbnailTopMargin.toFloat()
+ } else {
+ pivotX = (right - left) * 0.5f
+ pivotY = thumbnailTopMargin + (height - thumbnailTopMargin) * 0.5f
+ }
+ systemGestureExclusionRects =
+ SYSTEM_GESTURE_EXCLUSION_RECT.onEach {
+ it.right = width
+ it.bottom = height
+ }
+ }
+
+ override fun onRecycle() {
+ resetPersistentViewTransforms()
+ // Clear any references to the thumbnail (it will be re-read either from the cache or the
+ // system on next bind)
+ if (enableRefactorTaskThumbnail()) {
+ notifyIsRunningTaskUpdated()
+ } else {
+ taskContainers.forEach { it.thumbnailViewDeprecated.setThumbnail(it.task, null) }
+ }
+ setOverlayEnabled(false)
+ onTaskListVisibilityChanged(false)
+ borderEnabled = false
+ taskViewId = UNBOUND_TASK_VIEW_ID
+ taskContainers.forEach { it.destroy() }
+ }
+
+ // TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
+ override fun hasOverlappingRendering() = false
+
+ override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
+ super.onInitializeAccessibilityNodeInfo(info)
+ with(info) {
+ 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 ->
+ addAction(shortcut.createAccessibilityAction(context))
+ }
+ }
+ }
+
+ // Add DWB accessibility action at the end of the list
+ taskContainers.forEach {
+ it.digitalWellBeingToast?.getDWBAccessibilityAction()?.let(::addAction)
+ }
+
+ recentsView?.let {
+ collectionItemInfo =
+ AccessibilityNodeInfo.CollectionItemInfo.obtain(
+ 0,
+ 1,
+ it.taskViewCount - it.indexOfChild(this@TaskView) - 1,
+ 1,
+ false
+ )
+ }
+ }
+ }
+
+ 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
+ }
+
+ TaskOverlayFactory.getEnabledShortcuts(this, it).forEach { shortcut ->
+ if (shortcut.hasHandlerForAction(action)) {
+ shortcut.onClick(this)
+ return true
+ }
+ }
+ }
+
+ return super.performAccessibilityAction(action, arguments)
+ }
+
+ /** Updates this task view to the given {@param task}. */
+ open fun bind(
+ task: Task,
+ orientedState: RecentsOrientedState,
+ taskOverlayFactory: TaskOverlayFactory
+ ) {
+ cancelPendingLoadTasks()
+ taskContainers =
+ listOf(
+ createTaskContainer(
+ task,
+ R.id.snapshot,
+ R.id.icon,
+ R.id.show_windows,
+ STAGE_POSITION_UNDEFINED,
+ taskOverlayFactory
+ )
+ )
+ setOrientationState(orientedState)
+ }
+
+ protected fun createTaskContainer(
+ task: Task,
+ @IdRes thumbnailViewId: Int,
+ @IdRes iconViewId: Int,
+ @IdRes showWindowViewId: Int,
+ @StagePosition stagePosition: Int,
+ taskOverlayFactory: TaskOverlayFactory
+ ): TaskContainer {
+ val thumbnailViewDeprecated: TaskThumbnailViewDeprecated = findViewById(thumbnailViewId)!!
+ val thumbnailView: TaskThumbnailView?
+ if (enableRefactorTaskThumbnail()) {
+ val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated)
+ thumbnailView =
+ TaskThumbnailView(context).apply {
+ layoutParams = thumbnailViewDeprecated.layoutParams
+ addView(this, indexOfSnapshotView)
+ }
+ thumbnailViewDeprecated.visibility = GONE
+ } else {
+ thumbnailView = null
+ }
+ val iconView = getOrInflateIconView(iconViewId)
+ return TaskContainer(
+ task,
+ thumbnailView,
+ thumbnailViewDeprecated,
+ iconView,
+ TransformingTouchDelegate(iconView.asView()),
+ stagePosition,
+ DigitalWellBeingToast(container, this),
+ findViewById(showWindowViewId)!!,
+ taskOverlayFactory
+ )
+ .apply {
+ if (enableRefactorTaskThumbnail()) {
+ thumbnailViewDeprecated.setTaskOverlay(overlay)
+ bindThumbnailView()
+ } else {
+ thumbnailViewDeprecated.bind(task, overlay)
+ }
+ }
+ }
+
+ protected fun getOrInflateIconView(@IdRes iconViewId: Int): TaskViewIcon {
+ val iconView = findViewById<View>(iconViewId)!!
+ return iconView as? TaskViewIcon
+ ?: (iconView as ViewStub)
+ .apply {
+ layoutResource =
+ if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+ else R.layout.icon_view
+ }
+ .inflate() as TaskViewIcon
+ }
+
+ protected fun isTaskContainersInitialized() = this::taskContainers.isInitialized
+
+ fun containsMultipleTasks() = taskContainers.size > 1
+
+ /**
+ * Returns the TaskContainer corresponding to a given taskId, or null if the TaskView does not
+ * contain a Task with that ID.
+ */
+ fun getTaskContainerById(taskId: Int) = taskContainers.firstOrNull { it.task.key.id == taskId }
+
+ /** Check if given `taskId` is tracked in this view */
+ fun containsTaskId(taskId: Int) = getTaskContainerById(taskId) != null
+
+ open fun setOrientationState(orientationState: RecentsOrientedState) {
+ this.orientedState = orientationState
+ taskContainers.forEach { it.iconView.setIconOrientation(orientationState, isGridTask) }
+ setThumbnailOrientation(orientationState)
+ }
+
+ protected open fun setThumbnailOrientation(orientationState: RecentsOrientedState) {
+ taskContainers.forEach {
+ it.overlay.updateOrientationState(orientationState)
+ it.digitalWellBeingToast?.initialize(it.task)
+ }
+ }
+
+ /**
+ * Updates TaskView scaling and translation required to support variable width if enabled, while
+ * ensuring TaskView fits into screen in fullscreen.
+ */
+ fun updateTaskSize(
+ lastComputedTaskSize: Rect,
+ lastComputedGridTaskSize: Rect,
+ lastComputedCarouselTaskSize: Rect
+ ) {
+ val thumbnailPadding = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+ val taskWidth = lastComputedTaskSize.width()
+ val taskHeight = lastComputedTaskSize.height()
+ val nonGridScale: Float
+ val boxTranslationY: Float
+ val expectedWidth: Int
+ val expectedHeight: Int
+ if (container.deviceProfile.isTablet) {
+ val boxWidth: Int
+ val boxHeight: Int
+ if (isFocusedTask) {
+ // Task will be focused and should use focused task size. Use focusTaskRatio
+ // that is associated with the original orientation of the focused task.
+ boxWidth = taskWidth
+ boxHeight = taskHeight
+ } else {
+ // Otherwise task is in grid, and should use lastComputedGridTaskSize.
+ boxWidth = lastComputedGridTaskSize.width()
+ boxHeight = lastComputedGridTaskSize.height()
+ }
+
+ // Bound width/height to the box size.
+ expectedWidth = boxWidth
+ expectedHeight = boxHeight + thumbnailPadding
+
+ // Scale to to fit task Rect.
+ nonGridScale =
+ if (enableGridOnlyOverview()) {
+ lastComputedCarouselTaskSize.width() / taskWidth.toFloat()
+ } else {
+ taskWidth / boxWidth.toFloat()
+ }
+
+ // Align to top of task Rect.
+ boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f
+ } else {
+ nonGridScale = 1f
+ boxTranslationY = 0f
+ expectedWidth = if (enableOverviewIconMenu()) taskWidth else LayoutParams.MATCH_PARENT
+ expectedHeight =
+ if (enableOverviewIconMenu()) taskHeight + thumbnailPadding
+ else LayoutParams.MATCH_PARENT
+ }
+ this.nonGridScale = nonGridScale
+ this.boxTranslationY = boxTranslationY
+ updateLayoutParams<ViewGroup.LayoutParams> {
+ width = expectedWidth
+ height = expectedHeight
+ }
+ updateThumbnailSize()
+ }
+
+ 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> {
+ topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+ }
+ }
+
+ /** Returns the thumbnail's bounds, optionally relative to the screen. */
+ @JvmOverloads
+ open fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean = false) {
+ bounds.setEmpty()
+ taskContainers.forEach {
+ val thumbnailBounds = Rect()
+ if (relativeToDragLayer) {
+ container.dragLayer.getDescendantRectRelativeToSelf(
+ it.snapshotView,
+ thumbnailBounds
+ )
+ } else {
+ thumbnailBounds.set(it.snapshotView)
+ }
+ bounds.union(thumbnailBounds)
+ }
+ }
+
+ /**
+ * See [TaskDataChanges]
+ *
+ * @param visible If this task view will be visible to the user in overview or hidden
+ */
+ fun onTaskListVisibilityChanged(visible: Boolean) {
+ onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL)
+ }
+
+ /**
+ * See [TaskDataChanges]
+ *
+ * @param visible If this task view will be visible to the user in overview or hidden
+ */
+ open fun onTaskListVisibilityChanged(visible: Boolean, @TaskDataChanges changes: Int) {
+ cancelPendingLoadTasks()
+ val recentsModel = RecentsModel.INSTANCE.get(context)
+ // These calls are no-ops if the data is already loaded, try and load the high
+ // resolution thumbnail if the state permits
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL) && !enableRefactorTaskThumbnail()) {
+ taskContainers.forEach {
+ if (visible) {
+ recentsModel.thumbnailCache
+ .updateThumbnailInBackground(it.task) { thumbnailData ->
+ it.thumbnailViewDeprecated.setThumbnail(it.task, thumbnailData)
+ }
+ ?.also { request -> pendingThumbnailLoadRequests.add(request) }
+ } else {
+ it.thumbnailViewDeprecated.setThumbnail(null, null)
+ // Reset the task thumbnail reference as well (it will be fetched from the
+ // cache or reloaded next time we need it)
+ it.task.thumbnail = null
+ }
+ }
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ taskContainers.forEach {
+ if (visible) {
+ recentsModel.iconCache
+ .updateIconInBackground(it.task) { task ->
+ setIcon(it.iconView, task.icon)
+ if (enableOverviewIconMenu()) {
+ setText(it.iconView, task.title)
+ }
+ it.digitalWellBeingToast?.initialize(task)
+ }
+ ?.also { request -> pendingIconLoadRequests.add(request) }
+ } else {
+ setIcon(it.iconView, null)
+ if (enableOverviewIconMenu()) {
+ setText(it.iconView, null)
+ }
+ }
+ }
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) {
+ currentFullscreenParams.updateCornerRadius(context)
+ }
+ }
+
+ protected open fun needsUpdate(@TaskDataChanges dataChange: Int, @TaskDataChanges flag: Int) =
+ (dataChange and flag) == flag
+
+ protected open fun cancelPendingLoadTasks() {
+ pendingThumbnailLoadRequests.forEach { it.cancel() }
+ pendingThumbnailLoadRequests.clear()
+ pendingIconLoadRequests.forEach { it.cancel() }
+ pendingIconLoadRequests.clear()
+ }
+
+ protected fun setIcon(iconView: TaskViewIcon, icon: Drawable?) {
+ with(iconView) {
+ if (icon != null) {
+ setDrawable(icon)
+ setOnClickListener {
+ if (!confirmSecondSplitSelectApp()) {
+ showTaskMenu(this)
+ }
+ }
+ setOnLongClickListener {
+ requestDisallowInterceptTouchEvent(true)
+ showTaskMenu(this)
+ }
+ } else {
+ setDrawable(null)
+ setOnClickListener(null)
+ setOnLongClickListener(null)
+ }
+ }
+ }
+
+ protected fun setText(iconView: TaskViewIcon, text: CharSequence?) {
+ iconView.setText(text)
+ }
+
+ open fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData?>?) {
+ if (enableRefactorTaskThumbnail()) {
+ // TODO(b/342560598) add thumbnail logic
+ return
+ }
+
+ taskContainers.forEach {
+ val thumbnailData = thumbnailDatas?.get(it.task.key.id)
+ if (thumbnailData != null) {
+ it.thumbnailViewDeprecated.setThumbnail(it.task, thumbnailData)
+ } else {
+ it.thumbnailViewDeprecated.refresh()
+ }
+ }
+ }
+
+ private fun onClick() {
+ if (confirmSecondSplitSelectApp()) {
+ Log.d("b/310064698", "${taskIds.contentToString()} - onClick - split select is active")
+ return
+ }
+ val callbackList =
+ launchTasks()?.apply {
+ add {
+ Log.d("b/310064698", "${taskIds.contentToString()} - onClick - launchCompleted")
+ }
+ }
+ Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList")
+ container.statsLogManager
+ .logger()
+ .withItemInfo(firstItemInfo)
+ .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
+ }
+
+ /**
+ * Starts the task associated with this view and animates the startup.
+ *
+ * @return CompletionStage to indicate the animation completion or null if the launch failed.
+ */
+ open fun launchTaskAnimated(): RunnableList? {
+ TestLogging.recordEvent(
+ TestProtocol.SEQUENCE_MAIN,
+ "startActivityFromRecentsAsync",
+ taskIds.contentToString()
+ )
+ val opts =
+ container.getActivityLaunchOptions(this, null).apply {
+ options.launchDisplayId = display?.displayId ?: Display.DEFAULT_DISPLAY
+ }
+ if (
+ ActivityManagerWrapper.getInstance()
+ .startActivityFromRecents(taskContainers[0].task.key, opts.options)
+ ) {
+ Log.d(
+ TAG,
+ "launchTaskAnimated - startActivityFromRecents: ${taskIds.contentToString()}"
+ )
+ ActiveGestureLog.INSTANCE.trackEvent(
+ ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED
+ )
+ val recentsView = recentsView ?: return null
+ if (recentsView.runningTaskViewId != -1) {
+ recentsView.onTaskLaunchedInLiveTileMode()
+
+ // Return a fresh callback in the live tile case, so that it's not accidentally
+ // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner.
+ return RunnableList().also { recentsView.addSideTaskLaunchCallback(it) }
+ }
+ if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ // If the recents transition is running (ie. in live tile mode), then the start
+ // of a new task will merge into the existing transition and it currently will
+ // not be run independently, so we need to rely on the onTaskAppeared() call
+ // for the new task to trigger the side launch callback to flush this runnable
+ // list (which is usually flushed when the app launch animation finishes)
+ recentsView.addSideTaskLaunchCallback(opts.onEndCallback)
+ }
+ return opts.onEndCallback
+ } else {
+ notifyTaskLaunchFailed()
+ return null
+ }
+ }
+
+ /** Starts the task associated with this view without any animation */
+ fun launchTask(callback: (launched: Boolean) -> Unit) {
+ launchTask(callback, isQuickSwitch = false)
+ }
+
+ /** Starts the task associated with this view without any animation */
+ open fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) {
+ 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) {
+ notifyTaskLaunchFailed()
+ recentsView?.let {
+ // Disable animations for now, as it is an edge case and the app usually
+ // covers launcher and also any state transition animation also gets
+ // clobbered by QuickstepTransitionManager.createWallpaperOpenAnimations
+ // when launcher shows again
+ it.startHome(false /* animated */)
+ // LauncherTaskbarUIController depends on the launcher state when
+ // checking whether to handle resume, but that can come in before
+ // startHome() changes the state, so force-refresh here to ensure the
+ // taskbar is updated
+ it.mSizeStrategy.taskbarController?.refreshResumedState()
+ }
+ }
+ }
+ // Indicate success once the system has indicated that the transition has started
+ val opts =
+ ActivityOptions.makeCustomTaskAnimation(
+ context,
+ 0,
+ 0,
+ Executors.MAIN_EXECUTOR.handler,
+ { callback(true) }
+ ) {
+ failureListener.onTransitionFinished()
+ }
+ .apply {
+ launchDisplayId = display?.displayId ?: Display.DEFAULT_DISPLAY
+ if (isQuickSwitch) {
+ setFreezeRecentTasksReordering()
+ }
+ // TODO(b/334826842) add splash functionality to new TTV
+ if (!enableRefactorTaskThumbnail()) {
+ disableStartingWindow =
+ firstContainer.thumbnailViewDeprecated.shouldShowSplashView()
+ }
+ }
+ Executors.UI_HELPER_EXECUTOR.execute {
+ if (
+ !ActivityManagerWrapper.getInstance()
+ .startActivityFromRecents(firstContainer.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
+ // above
+ Executors.MAIN_EXECUTOR.post {
+ notifyTaskLaunchFailed()
+ callback(false)
+ }
+ }
+ Log.d(TAG, "launchTask - startActivityFromRecents: ${taskIds.contentToString()}")
+ }
+ }
+
+ /** Launch of the current task (both live and inactive tasks) with an animation. */
+ fun launchTasks(): RunnableList? {
+ val recentsView = recentsView ?: return null
+ val remoteTargetHandles = recentsView.mRemoteTargetHandles
+ if (!isRunningTask || remoteTargetHandles == null) {
+ return launchTaskAnimated()
+ }
+ if (!isClickableAsLiveTile) {
+ Log.e(TAG, "TaskView is not clickable as a live tile; returning to home.")
+ return null
+ }
+ isClickableAsLiveTile = false
+ val targets =
+ if (remoteTargetHandles.size == 1) {
+ remoteTargetHandles[0].transformParams.targetSet
+ } else {
+ val apps =
+ remoteTargetHandles.flatMap { it.transformParams.targetSet.apps.asIterable() }
+ val wallpapers =
+ remoteTargetHandles.flatMap {
+ it.transformParams.targetSet.wallpapers.asIterable()
+ }
+ RemoteAnimationTargets(
+ apps.toTypedArray(),
+ wallpapers.toTypedArray(),
+ remoteTargetHandles[0].transformParams.targetSet.nonApps,
+ remoteTargetHandles[0].transformParams.targetSet.targetMode
+ )
+ }
+ if (targets == null) {
+ // If the recents animation is cancelled somehow between the parent if block and
+ // here, try to launch the task as a non live tile task.
+ val runnableList = launchTaskAnimated()
+ if (runnableList == null) {
+ Log.e(
+ TAG,
+ "Recents animation cancelled and cannot launch task as non-live tile" +
+ "; returning to home"
+ )
+ }
+ isClickableAsLiveTile = true
+ return runnableList
+ }
+ val runnableList = RunnableList()
+ with(AnimatorSet()) {
+ TaskViewUtils.composeRecentsLaunchAnimator(
+ this,
+ this@TaskView,
+ targets.apps,
+ targets.wallpapers,
+ targets.nonApps,
+ true /* launcherClosing */,
+ recentsView.stateManager,
+ recentsView,
+ recentsView.depthController
+ )
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animator: Animator) {
+ if (taskContainers.any { it.task.key.displayId != rootViewDisplayId }) {
+ launchTaskAnimated()
+ }
+ isClickableAsLiveTile = true
+ runEndCallback()
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ runEndCallback()
+ }
+
+ private fun runEndCallback() {
+ runnableList.executeAllAndDestroy()
+ }
+ }
+ )
+ start()
+ }
+ Log.d(TAG, "launchTasks - composeRecentsLaunchAnimator: ${taskIds.contentToString()}")
+ recentsView.onTaskLaunchedInLiveTileMode()
+ return runnableList
+ }
+
+ private fun notifyTaskLaunchFailed() {
+ val sb = StringBuilder("Failed to launch task \n")
+ taskContainers.forEach {
+ sb.append("(task=${it.task.key.baseIntent} userId=${it.task.key.userId})\n")
+ }
+ Log.w(TAG, sb.toString())
+ 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
+ */
+ protected open fun confirmSecondSplitSelectApp(): Boolean {
+ val index = getLastSelectedChildTaskIndex()
+ if (index >= taskContainers.size) {
+ return false
+ }
+ val container = taskContainers[index]
+ val recentsView = recentsView ?: return false
+ return recentsView.confirmSplitSelect(
+ this,
+ container.task,
+ container.iconView.drawable,
+ container.thumbnailViewDeprecated,
+ container.thumbnailViewDeprecated.thumbnail, /* intent */
+ null, /* user */
+ null,
+ container.itemInfo
+ )
+ }
+
+ /**
+ * Returns the task index of the last selected child task (0 or 1). If we contain multiple tasks
+ * and this TaskView is used as part of split selection, the selected child task index will be
+ * that of the remaining task.
+ */
+ protected open fun getLastSelectedChildTaskIndex() = 0
+
+ private fun showTaskMenu(iconView: TaskViewIcon): Boolean {
+ val recentsView = recentsView ?: return false
+ if (!recentsView.canLaunchFullscreenTask()) {
+ // Don't show menu when selecting second split screen app
+ return true
+ }
+ if (!container.deviceProfile.isTablet && !recentsView.isClearAllHidden) {
+ recentsView.snapToPage(recentsView.indexOfChild(this))
+ return false
+ }
+ val menuContainer = taskContainers.firstOrNull { it.iconView === iconView } ?: return false
+ container.statsLogManager
+ .logger()
+ .withItemInfo(menuContainer.itemInfo)
+ .log(LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS)
+ return showTaskMenuWithContainer(menuContainer)
+ }
+
+ private fun showTaskMenuWithContainer(menuContainer: TaskContainer): Boolean {
+ val recentsView = recentsView ?: return false
+ return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
+ menuContainer.iconView.revealAnim(/* isRevealing= */ true)
+ TaskMenuView.showForTask(menuContainer) {
+ menuContainer.iconView.revealAnim(/* isRevealing= */ false)
+ }
+ } else if (container.deviceProfile.isTablet) {
+ val alignedOptionIndex =
+ if (
+ recentsView.isOnGridBottomRow(menuContainer.taskView) &&
+ container.deviceProfile.isLandscape
+ ) {
+ if (enableGridOnlyOverview()) {
+ // With no focused task, there is less available space below the tasks, so
+ // align the arrow to the third option in the menu.
+ 2
+ } else {
+ // Bottom row of landscape grid aligns arrow to second option to avoid
+ // clipping
+ 1
+ }
+ } else {
+ 0
+ }
+ TaskMenuViewWithArrow.showForTask(menuContainer, alignedOptionIndex)
+ } else {
+ TaskMenuView.showForTask(menuContainer)
+ }
+ }
+
+ /**
+ * Whether the taskview should take the touch event from parent. Events passed to children that
+ * might require special handling.
+ */
+ open fun offerTouchToChildren(event: MotionEvent): Boolean {
+ taskContainers.forEach {
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ computeAndSetIconTouchDelegate(it.iconView, tempCoordinates, it.iconTouchDelegate)
+ if (it.iconTouchDelegate.onTouchEvent(event)) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ private fun computeAndSetIconTouchDelegate(
+ view: TaskViewIcon,
+ tempCenterCoordinates: FloatArray,
+ transformingTouchDelegate: TransformingTouchDelegate
+ ) {
+ val viewHalfWidth = view.width / 2f
+ val viewHalfHeight = view.height / 2f
+ Utilities.getDescendantCoordRelativeToAncestor(
+ view.asView(),
+ container.dragLayer,
+ tempCenterCoordinates.apply {
+ this[0] = viewHalfWidth
+ this[1] = viewHalfHeight
+ },
+ false
+ )
+ transformingTouchDelegate.setBounds(
+ (tempCenterCoordinates[0] - viewHalfWidth).toInt(),
+ (tempCenterCoordinates[1] - viewHalfHeight).toInt(),
+ (tempCenterCoordinates[0] + viewHalfWidth).toInt(),
+ (tempCenterCoordinates[1] + viewHalfHeight).toInt()
+ )
+ }
+
+ /** Sets up an on-click listener and the visibility for show_windows icon on top of the task. */
+ open fun setUpShowAllInstancesListener() {
+ taskContainers.forEach {
+ it.showWindowsView?.let { showWindowsView ->
+ updateFilterCallback(
+ showWindowsView,
+ getFilterUpdateCallback(it.task.key.packageName)
+ )
+ }
+ }
+ }
+
+ /**
+ * Returns a callback that updates the state of the filter and the recents overview
+ *
+ * @param taskPackageName package name of the task to filter by
+ */
+ private fun getFilterUpdateCallback(taskPackageName: String?) =
+ if (recentsView?.filterState?.shouldShowFilterUI(taskPackageName) == true)
+ OnClickListener { recentsView?.setAndApplyFilter(taskPackageName) }
+ else null
+
+ /**
+ * Sets the correct visibility and callback on the provided filterView based on whether the
+ * callback is null or not
+ */
+ private fun updateFilterCallback(filterView: View, callback: OnClickListener?) {
+ // Filtering changes alpha instead of the visibility since visibility
+ // can be altered separately through RecentsView#resetFromSplitSelectionState()
+ with(filterView) {
+ alpha = if (callback == null) 0f else 1f
+ setOnClickListener(callback)
+ }
+ }
+
+ /**
+ * Called to animate a smooth transition when going directly from an app into Overview (and vice
+ * versa). Icons fade in, and DWB banners slide in with a "shift up" animation.
+ */
+ private fun onFocusTransitionProgressUpdated(focusTransitionProgress: Float) {
+ taskContainers.forEach {
+ it.iconView.setContentAlpha(focusTransitionProgress)
+ it.digitalWellBeingToast?.updateBannerOffset(1f - focusTransitionProgress)
+ }
+ }
+
+ fun animateIconScaleAndDimIntoView() {
+ iconAndDimAnimator?.cancel()
+ iconAndDimAnimator =
+ ObjectAnimator.ofFloat(focusTransitionScaleAndDim, MULTI_PROPERTY_VALUE, 0f, 1f).apply {
+ duration = SCALE_ICON_DURATION
+ interpolator = Interpolators.LINEAR
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ iconAndDimAnimator = null
+ }
+ }
+ )
+ start()
+ }
+ }
+
+ fun setIconScaleAndDim(iconScale: Float) {
+ iconAndDimAnimator?.cancel()
+ focusTransitionScaleAndDim.value = iconScale
+ }
+
+ /** Set a color tint on the snapshot and supporting views. */
+ open fun setColorTint(amount: Float, tintColor: Int) {
+ taskContainers.forEach {
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334832108) Add scrim to new TTV
+ it.thumbnailViewDeprecated.dimAlpha = amount
+ }
+ it.iconView.setIconColorTint(tintColor, amount)
+ it.digitalWellBeingToast?.setBannerColorTint(tintColor, amount)
+ }
+ }
+
+ /**
+ * Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
+ * IconView is unaffected.
+ *
+ * @param taskId is only used when setting visibility to a non-[View.VISIBLE] value
+ */
+ open fun setThumbnailVisibility(visibility: Int, taskId: Int) {
+ taskContainers.forEach {
+ if (visibility == VISIBLE || it.task.key.id == taskId) {
+ it.snapshotView.visibility = visibility
+ it.digitalWellBeingToast?.setBannerVisibility(visibility)
+ it.showWindowsView?.visibility = visibility
+ it.overlay.setVisibility(visibility)
+ }
+ }
+ }
+
+ open fun setOverlayEnabled(overlayEnabled: Boolean) {
+ // TODO(b/335606129) Investigate the usage of [TaskOverlay] in the new TaskThumbnailView.
+ // and if it's still necessary we should support that in the new TTV class.
+ if (!enableRefactorTaskThumbnail()) {
+ taskContainers.forEach { it.thumbnailViewDeprecated.setOverlayEnabled(overlayEnabled) }
+ }
+ }
+
+ protected open fun refreshTaskThumbnailSplash() {
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334826842) add splash functionality to new TTV
+ taskContainers.forEach { it.thumbnailViewDeprecated.refreshSplashView() }
+ }
+ }
+
+ protected fun getScrollAdjustment(gridEnabled: Boolean) =
+ if (gridEnabled) gridTranslationX else nonGridTranslationX
+
+ protected fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled)
+
+ fun getSizeAdjustment(fullscreenEnabled: Boolean) = if (fullscreenEnabled) nonGridScale else 1f
+
+ private fun applyScale() {
+ val scale = persistentScale * dismissScale
+ scaleX = scale
+ scaleY = scale
+ if (enableRefactorTaskThumbnail()) {
+ taskViewData.scale.value = scale
+ }
+ updateSnapshotRadius()
+ }
+
+ protected open fun applyThumbnailSplashAlpha() {
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334826842) add splash functionality to new TTV
+ taskContainers.forEach {
+ it.thumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha)
+ }
+ }
+ }
+
+ private fun applyTranslationX() {
+ translationX =
+ dismissTranslationX +
+ taskOffsetTranslationX +
+ taskResistanceTranslationX +
+ splitSelectTranslationX +
+ gridEndTranslationX +
+ persistentTranslationX
+ }
+
+ private fun applyTranslationY() {
+ translationY =
+ dismissTranslationY +
+ taskOffsetTranslationY +
+ taskResistanceTranslationY +
+ splitSelectTranslationY +
+ persistentTranslationY
+ }
+
+ private fun onGridProgressChanged() {
+ applyTranslationX()
+ applyTranslationY()
+ applyScale()
+ }
+
+ protected open fun onFullscreenProgressChanged(fullscreenProgress: Float) {
+ taskContainers.forEach {
+ it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
+ it.overlay.setFullscreenProgress(fullscreenProgress)
+ }
+ focusTransitionFullscreen.value =
+ FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
+ updateSnapshotRadius()
+ }
+
+ protected open fun updateSnapshotRadius() {
+ updateCurrentFullscreenParams()
+ taskContainers.forEach {
+ it.thumbnailViewDeprecated.setFullscreenParams(getThumbnailFullscreenParams())
+ it.overlay.setFullscreenParams(getThumbnailFullscreenParams())
+ }
+ }
+
+ protected open fun updateCurrentFullscreenParams() {
+ updateFullscreenParams(currentFullscreenParams)
+ }
+
+ protected fun updateFullscreenParams(fullscreenParams: FullscreenDrawParams) {
+ recentsView?.let { fullscreenParams.setProgress(fullscreenProgress, it.scaleX, scaleX) }
+ }
+
+ protected open fun getThumbnailFullscreenParams(): FullscreenDrawParams =
+ currentFullscreenParams
+
+ private fun onModalnessUpdated(modalness: Float) {
+ taskContainers.forEach {
+ it.iconView.setModalAlpha(1 - modalness)
+ it.digitalWellBeingToast?.updateBannerOffset(modalness)
+ }
+ }
+
+ /** Updates [TaskThumbnailView] to reflect the latest [Task] state (i.e., task isRunning). */
+ fun notifyIsRunningTaskUpdated() {
+ // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
+ // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
+ taskContainers.forEach { it.bindThumbnailView() }
+ }
+
+ fun resetPersistentViewTransforms() {
+ nonGridTranslationX = 0f
+ gridTranslationX = 0f
+ gridTranslationY = 0f
+ boxTranslationY = 0f
+ nonGridPivotTranslationX = 0f
+ resetViewTransforms()
+ }
+
+ open fun resetViewTransforms() {
+ // fullscreenTranslation and accumulatedTranslation should not be reset, as
+ // resetViewTransforms is called during QuickSwitch scrolling.
+ dismissTranslationX = 0f
+ taskOffsetTranslationX = 0f
+ taskResistanceTranslationX = 0f
+ splitSelectTranslationX = 0f
+ gridEndTranslationX = 0f
+ dismissTranslationY = 0f
+ taskOffsetTranslationY = 0f
+ taskResistanceTranslationY = 0f
+ if (recentsView?.isSplitSelectionActive != true) {
+ splitSelectTranslationY = 0f
+ }
+ dismissScale = 1f
+ translationZ = 0f
+ alpha = stableAlpha
+ setIconScaleAndDim(1f)
+ setColorTint(0f, 0)
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/335399428) add split select functionality to new TTV
+ taskContainers.forEach { it.thumbnailViewDeprecated.resetViewTransforms() }
+ }
+ }
+
+ private fun getGridTrans(endTranslation: Float) =
+ Utilities.mapRange(gridProgress, 0f, endTranslation)
+
+ private fun getNonGridTrans(endTranslation: Float) =
+ endTranslation - getGridTrans(endTranslation)
+
+ /** We update and subsequently draw these in [fullscreenProgress]. */
+ open class FullscreenDrawParams(context: Context) : SafeCloseable {
+ var cornerRadius = 0f
+ private var windowCornerRadius = 0f
+ var currentDrawnCornerRadius = 0f
+
+ init {
+ updateCornerRadius(context)
+ }
+
+ /** Recomputes the start and end corner radius for the given Context. */
+ fun updateCornerRadius(context: Context) {
+ cornerRadius = computeTaskCornerRadius(context)
+ windowCornerRadius = computeWindowCornerRadius(context)
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ open fun computeTaskCornerRadius(context: Context): Float {
+ return TaskCornerRadius.get(context)
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ open fun computeWindowCornerRadius(context: Context): Float {
+ val activityContext: ActivityContext? = ActivityContext.lookupContextNoThrow(context)
+
+ // The corner radius is fixed to match when Taskbar is persistent mode
+ return if (
+ activityContext != null &&
+ activityContext.deviceProfile?.isTaskbarPresent == true &&
+ DisplayController.isTransientTaskbar(context)
+ ) {
+ context.resources
+ .getDimensionPixelSize(R.dimen.persistent_taskbar_corner_radius)
+ .toFloat()
+ } else {
+ QuickStepContract.getWindowCornerRadius(context)
+ }
+ }
+
+ /** Sets the progress in range [0, 1] */
+ fun setProgress(fullscreenProgress: Float, parentScale: Float, taskViewScale: Float) {
+ currentDrawnCornerRadius =
+ Utilities.mapRange(fullscreenProgress, cornerRadius, windowCornerRadius) /
+ parentScale /
+ taskViewScale
+ }
+
+ override fun close() {}
+ }
+
+ /** Holder for all Task dependent information. */
+ inner class TaskContainer(
+ val task: Task,
+ val thumbnailView: TaskThumbnailView?,
+ val thumbnailViewDeprecated: TaskThumbnailViewDeprecated,
+ val iconView: TaskViewIcon,
+ /**
+ * This technically can be a vanilla [android.view.TouchDelegate] class, however that class
+ * requires setting the touch bounds at construction, so we'd repeatedly be created many
+ * instances unnecessarily as scrolling occurs, whereas [TransformingTouchDelegate] allows
+ * touch delegated bounds only to be updated.
+ */
+ val iconTouchDelegate: TransformingTouchDelegate,
+ /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
+ @StagePosition val stagePosition: Int,
+ val digitalWellBeingToast: DigitalWellBeingToast?,
+ val showWindowsView: View?,
+ taskOverlayFactory: TaskOverlayFactory
+ ) {
+ val overlay: TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
+
+ val snapshotView: View
+ get() = thumbnailView ?: thumbnailViewDeprecated
+
+ /** Builds proto for logging */
+ val itemInfo: WorkspaceItemInfo
+ get() =
+ WorkspaceItemInfo().apply {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK
+ container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER
+ val componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key)
+ user = componentKey.user
+ intent = Intent().setComponent(componentKey.componentName)
+ title = task.title
+ recentsView?.let { screenId = it.indexOfChild(this@TaskView) }
+ if (privateSpaceRestrictAccessibilityDrag()) {
+ if (
+ UserCache.getInstance(context).getUserInfo(componentKey.user).isPrivate
+ ) {
+ runtimeStatusFlags =
+ runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+ }
+ }
+ }
+
+ val taskView: TaskView
+ get() = this@TaskView
+
+ fun destroy() {
+ digitalWellBeingToast?.destroy()
+ thumbnailView?.let { taskView.removeView(it) }
+ }
+
+ // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
+ // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
+ fun bindThumbnailView() {
+ // TODO(b/343364498): Existing view has shouldShowScreenshot as an override as well but
+ // this should be decided inside TaskThumbnailViewModel.
+ thumbnailView?.viewModel?.bind(TaskThumbnail(task.key.id, isRunningTask))
+ }
+ }
+
+ companion object {
+ private const val TAG = "TaskView"
+ const val FLAG_UPDATE_ICON = 1
+ const val FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON shl 1
+ const val FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL shl 1
+ const val FLAG_UPDATE_ALL =
+ (FLAG_UPDATE_ICON or FLAG_UPDATE_THUMBNAIL or FLAG_UPDATE_CORNER_RADIUS)
+
+ const val FOCUS_TRANSITION_INDEX_FULLSCREEN = 0
+ const val FOCUS_TRANSITION_INDEX_SCALE_AND_DIM = 1
+ const val FOCUS_TRANSITION_INDEX_COUNT = 2
+
+ /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
+ const val MAX_PAGE_SCRIM_ALPHA = 0.4f
+ const val SCALE_ICON_DURATION: Long = 120
+ private const val DIM_ANIM_DURATION: Long = 700
+ private const val FOCUS_TRANSITION_THRESHOLD =
+ SCALE_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
+ val FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR =
+ Interpolators.clampToProgress(
+ Interpolators.FAST_OUT_SLOW_IN,
+ 1f - FOCUS_TRANSITION_THRESHOLD,
+ 1f
+ )!!
+ private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
+
+ private val FOCUS_TRANSITION: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("focusTransition") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.focusTransitionProgress = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.focusTransitionProgress
+ }
+
+ private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("splitSelectTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.splitSelectTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.splitSelectTranslationX
+ }
+
+ private val SPLIT_SELECT_TRANSLATION_Y: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("splitSelectTranslationY") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.splitSelectTranslationY = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.splitSelectTranslationY
+ }
+
+ private val DISMISS_TRANSLATION_X: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("dismissTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.dismissTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.dismissTranslationX
+ }
+
+ private val DISMISS_TRANSLATION_Y: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("dismissTranslationY") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.dismissTranslationY = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.dismissTranslationY
+ }
+
+ private val TASK_OFFSET_TRANSLATION_X: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("taskOffsetTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.taskOffsetTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.taskOffsetTranslationX
+ }
+
+ private val TASK_OFFSET_TRANSLATION_Y: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("taskOffsetTranslationY") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.taskOffsetTranslationY = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.taskOffsetTranslationY
+ }
+
+ private val TASK_RESISTANCE_TRANSLATION_X: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("taskResistanceTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.taskResistanceTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.taskResistanceTranslationX
+ }
+
+ private val TASK_RESISTANCE_TRANSLATION_Y: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("taskResistanceTranslationY") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.taskResistanceTranslationY = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.taskResistanceTranslationY
+ }
+
+ @JvmField
+ val GRID_END_TRANSLATION_X: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("gridEndTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.gridEndTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.gridEndTranslationX
+ }
+
+ @JvmField
+ val DISMISS_SCALE: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("dismissScale") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.dismissScale = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.dismissScale
+ }
+ }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
similarity index 91%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
index 58be345..0f06d98 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -12,10 +12,10 @@
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.SCREEN_PIN_LONG_PRESS_THRESHOLD;
-import static com.android.quickstep.OverviewCommandHelper.TYPE_HOME;
-import static com.android.quickstep.OverviewCommandHelper.TYPE_TOGGLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
@@ -31,7 +31,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.quickstep.OverviewCommandHelper;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.AssistUtils;
@@ -52,8 +52,6 @@
@Mock
TouchInteractionService mockService;
@Mock
- OverviewCommandHelper mockCommandHelper;
- @Mock
Handler mockHandler;
@Mock
AssistUtils mockAssistUtils;
@@ -68,13 +66,26 @@
@Mock
View mockView;
+ private int mHomePressCount;
+ private int mOverviewToggleCount;
+ private final TaskbarNavButtonCallbacks mCallbacks = new TaskbarNavButtonCallbacks() {
+ @Override
+ public void onNavigateHome() {
+ mHomePressCount++;
+ }
+
+ @Override
+ public void onToggleOverview() {
+ mOverviewToggleCount++;
+ }
+ };
+
private TaskbarNavButtonController mNavButtonController;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mockService.getDisplayId()).thenReturn(DISPLAY_ID);
- when(mockService.getOverviewCommandHelper()).thenReturn(mockCommandHelper);
when(mockService.getApplicationContext())
.thenReturn(InstrumentationRegistry.getInstrumentation().getTargetContext()
.getApplicationContext());
@@ -82,8 +93,12 @@
when(mockTaskbarControllers.getTaskbarActivityContext())
.thenReturn(mockTaskbarActivityContext);
doReturn(mockStatsLogManager).when(mockTaskbarActivityContext).getStatsLogManager();
- mNavButtonController = new TaskbarNavButtonController(mockService,
- mockSystemUiProxy, mockHandler, mockAssistUtils);
+ mNavButtonController = new TaskbarNavButtonController(
+ mockService,
+ mCallbacks,
+ mockSystemUiProxy,
+ mockHandler,
+ mockAssistUtils);
}
@Test
@@ -154,20 +169,20 @@
@Test
public void testPressHome() {
mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
- verify(mockCommandHelper, times(1)).addCommand(TYPE_HOME);
+ assertThat(mHomePressCount).isEqualTo(1);
}
@Test
public void testPressRecents() {
mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
- verify(mockCommandHelper, times(1)).addCommand(TYPE_TOGGLE);
+ assertThat(mOverviewToggleCount).isEqualTo(1);
}
@Test
- public void testPressRecentsWithScreenPinned() {
+ public void testPressRecentsWithScreenPinned_noNavigationToOverview() {
mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
- verify(mockCommandHelper, times(0)).addCommand(TYPE_TOGGLE);
+ assertThat(mOverviewToggleCount).isEqualTo(0);
}
@Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
new file mode 100644
index 0000000..a999e7f
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.app.Instrumentation
+import android.app.PendingIntent
+import android.content.IIntentSender
+import android.content.Intent
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ServiceTestRule
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
+import com.android.quickstep.AllAppsActionManager
+import com.android.quickstep.TouchInteractionService
+import com.android.quickstep.TouchInteractionService.TISBinder
+import org.junit.Assume.assumeTrue
+import org.junit.rules.MethodRule
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
+
+/**
+ * Manages the Taskbar lifecycle for unit tests.
+ *
+ * See [InjectController] for grabbing controller(s) under test with minimal boilerplate.
+ *
+ * The rule interacts with [TaskbarManager] on the main thread. A good rule of thumb for tests is
+ * that code that is executed on the main thread in production should also happen on that thread
+ * when tested.
+ *
+ * `@UiThreadTest` is a simple way to run an entire test body on the main thread. But if a test
+ * executes code that appends message(s) to the main thread's `MessageQueue`, the annotation will
+ * prevent those messages from being processed until after the test body finishes.
+ *
+ * To test pending messages, instead use something like [Instrumentation.runOnMainSync] to perform
+ * only sections of the test body on the main thread synchronously:
+ * ```
+ * @Test
+ * fun example() {
+ * instrumentation.runOnMainSync { doWorkThatPostsMessage() }
+ * // Second lambda will not execute until message is processed.
+ * instrumentation.runOnMainSync { verifyMessageResults() }
+ * }
+ * ```
+ */
+class TaskbarUnitTestRule : MethodRule {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val serviceTestRule = ServiceTestRule()
+
+ private lateinit var taskbarManager: TaskbarManager
+ private lateinit var target: Any
+
+ val activityContext: TaskbarActivityContext
+ get() {
+ return taskbarManager.currentActivityContext
+ ?: throw RuntimeException("Failed to obtain TaskbarActivityContext.")
+ }
+
+ override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ [email protected] = target
+
+ val context = instrumentation.targetContext
+ instrumentation.runOnMainSync {
+ assumeTrue(
+ LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent
+ )
+ }
+
+ // Check for existing Taskbar instance from Launcher process.
+ val launcherTaskbarManager: TaskbarManager? =
+ if (!isRunningInRobolectric) {
+ try {
+ val tisBinder =
+ serviceTestRule.bindService(
+ Intent(context, TouchInteractionService::class.java)
+ ) as? TISBinder
+ tisBinder?.taskbarManager
+ } catch (_: Exception) {
+ null
+ }
+ } else {
+ null
+ }
+
+ instrumentation.runOnMainSync {
+ taskbarManager =
+ TaskbarManager(
+ context,
+ AllAppsActionManager(context, UI_HELPER_EXECUTOR) {
+ PendingIntent(IIntentSender.Default())
+ },
+ object : TaskbarNavButtonCallbacks {},
+ )
+ }
+
+ try {
+ // Replace Launcher Taskbar window with test instance.
+ instrumentation.runOnMainSync {
+ launcherTaskbarManager?.removeTaskbarRootViewFromWindow()
+ taskbarManager.onUserUnlocked() // Required to complete initialization.
+ }
+
+ injectControllers()
+ base.evaluate()
+ } finally {
+ // Revert Taskbar window.
+ instrumentation.runOnMainSync {
+ taskbarManager.destroy()
+ launcherTaskbarManager?.addTaskbarRootViewToWindow()
+ }
+ }
+ }
+ }
+ }
+
+ /** Simulates Taskbar recreation lifecycle. */
+ fun recreateTaskbar() {
+ taskbarManager.recreateTaskbar()
+ injectControllers()
+ }
+
+ private fun injectControllers() {
+ val controllers = activityContext.controllers
+ val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type }
+ target.javaClass.fields
+ .filter { it.isAnnotationPresent(InjectController::class.java) }
+ .forEach {
+ it.set(
+ target,
+ controllerFieldsByType[it.type]?.get(controllers)
+ ?: throw NoSuchElementException("Failed to find controller for ${it.type}"),
+ )
+ }
+ }
+
+ /**
+ * Annotates test controller fields to inject the corresponding controllers from the current
+ * [TaskbarControllers] instance.
+ *
+ * Controllers are injected during test setup and upon calling [recreateTaskbar].
+ *
+ * Multiple controllers can be injected if needed.
+ */
+ @Retention(AnnotationRetention.RUNTIME)
+ @Target(AnnotationTarget.FIELD)
+ annotation class InjectController
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
new file mode 100644
index 0000000..c09dcf2
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.allapps
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Process
+import androidx.test.annotation.UiThreadTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.appprediction.PredictionRowView
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.notification.NotificationKeyData
+import com.android.launcher3.taskbar.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayController
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.launcher3.util.PackageUserKey
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+class TaskbarAllAppsControllerTest {
+
+ @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule()
+
+ @InjectController lateinit var allAppsController: TaskbarAllAppsController
+ @InjectController lateinit var overlayController: TaskbarOverlayController
+
+ @Test
+ @UiThreadTest
+ fun testToggle_once_showsAllApps() {
+ allAppsController.toggle()
+ assertThat(allAppsController.isOpen).isTrue()
+ }
+
+ @Test
+ @UiThreadTest
+ fun testToggle_twice_closesAllApps() {
+ allAppsController.toggle()
+ allAppsController.toggle()
+ assertThat(allAppsController.isOpen).isFalse()
+ }
+
+ @Test
+ @UiThreadTest
+ fun testToggle_taskbarRecreated_allAppsReopened() {
+ allAppsController.toggle()
+ taskbarUnitTestRule.recreateTaskbar()
+ assertThat(allAppsController.isOpen).isTrue()
+ }
+
+ @Test
+ @UiThreadTest
+ fun testSetApps_beforeOpened_cachesInfo() {
+ allAppsController.setApps(TEST_APPS, 0, emptyMap())
+ allAppsController.toggle()
+
+ val overlayContext = overlayController.requestWindow()
+ assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testSetApps_afterOpened_updatesStore() {
+ allAppsController.toggle()
+ allAppsController.setApps(TEST_APPS, 0, emptyMap())
+
+ val overlayContext = overlayController.requestWindow()
+ assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testSetPredictedApps_beforeOpened_cachesInfo() {
+ allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
+ allAppsController.toggle()
+
+ val predictedApps =
+ overlayController
+ .requestWindow()
+ .appsView
+ .floatingHeaderView
+ .findFixedRowByType(PredictionRowView::class.java)
+ .predictedApps
+ assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testSetPredictedApps_afterOpened_cachesInfo() {
+ allAppsController.toggle()
+ allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
+
+ val predictedApps =
+ overlayController
+ .requestWindow()
+ .appsView
+ .floatingHeaderView
+ .findFixedRowByType(PredictionRowView::class.java)
+ .predictedApps
+ assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS)
+ }
+
+ @Test
+ fun testUpdateNotificationDots_appInfo_hasDot() {
+ getInstrumentation().runOnMainSync {
+ allAppsController.setApps(TEST_APPS, 0, emptyMap())
+ allAppsController.toggle()
+ taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted(
+ PackageUserKey.fromItemInfo(TEST_APPS[0]),
+ NotificationKeyData("key"),
+ )
+ }
+
+ // Ensure the recycler view fully inflates before trying to grab an icon.
+ getInstrumentation().runOnMainSync {
+ val btv =
+ overlayController
+ .requestWindow()
+ .appsView
+ .activeRecyclerView
+ .findViewHolderForAdapterPosition(0)
+ ?.itemView as? BubbleTextView
+ assertThat(btv?.hasDot()).isTrue()
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ fun testUpdateNotificationDots_predictedApp_hasDot() {
+ allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
+ allAppsController.toggle()
+
+ taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted(
+ PackageUserKey.fromItemInfo(TEST_PREDICTED_APPS[0]),
+ NotificationKeyData("key"),
+ )
+
+ val predictionRowView =
+ overlayController
+ .requestWindow()
+ .appsView
+ .floatingHeaderView
+ .findFixedRowByType(PredictionRowView::class.java)
+ val btv = predictionRowView.getChildAt(0) as BubbleTextView
+ assertThat(btv.hasDot()).isTrue()
+ }
+
+ private companion object {
+ private val TEST_APPS =
+ Array(16) {
+ AppInfo(
+ ComponentName(
+ getInstrumentation().context,
+ "com.android.launcher3.tests.Activity$it",
+ ),
+ "Test App $it",
+ Process.myUserHandle(),
+ Intent(),
+ )
+ }
+
+ private val TEST_PREDICTED_APPS = TEST_APPS.take(4).map { WorkspaceItemInfo(it) }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/OWNERS b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/OWNERS
new file mode 100644
index 0000000..3f947a0
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/OWNERS
@@ -0,0 +1,5 @@
[email protected]
[email protected]
[email protected]
[email protected]
+
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
new file mode 100644
index 0000000..cc579ab
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles.animation
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Path
+import android.graphics.drawable.ColorDrawable
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.VISIBLE
+import android.widget.FrameLayout
+import androidx.core.graphics.drawable.toBitmap
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
+import com.android.launcher3.taskbar.bubbles.BubbleBarOverflow
+import com.android.launcher3.taskbar.bubbles.BubbleBarView
+import com.android.launcher3.taskbar.bubbles.BubbleStashController
+import com.android.launcher3.taskbar.bubbles.BubbleView
+import com.android.wm.shell.common.bubbles.BubbleInfo
+import com.android.wm.shell.shared.animation.PhysicsAnimator
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleBarViewAnimatorTest {
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var animatorScheduler: TestBubbleBarViewAnimatorScheduler
+ private lateinit var overflowView: BubbleView
+ private lateinit var bubbleView: BubbleView
+ private lateinit var bubble: BubbleBarBubble
+ private lateinit var bubbleBarView: BubbleBarView
+ private lateinit var bubbleStashController: BubbleStashController
+
+ @Before
+ fun setUp() {
+ animatorScheduler = TestBubbleBarViewAnimatorScheduler()
+ PhysicsAnimatorTestUtils.prepareForTest()
+ }
+
+ @Test
+ fun animateBubbleInForStashed() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateBubbleInForStashed(bubble)
+ }
+
+ // let the animation start and wait for it to complete
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ assertThat(handle.alpha).isEqualTo(0)
+ assertThat(handle.translationY)
+ .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR)
+ assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
+ assertThat(bubbleBarView.scaleX).isEqualTo(1)
+ assertThat(bubbleBarView.scaleY).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+
+ // execute the hide bubble animation
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+ // let the animation start and wait for it to complete
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ assertThat(handle.alpha).isEqualTo(1)
+ assertThat(handle.translationY).isEqualTo(0)
+ assertThat(bubbleBarView.alpha).isEqualTo(0)
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ verify(bubbleStashController).stashBubbleBarImmediate()
+ }
+
+ @Test
+ fun animateBubbleInForStashed_tapAnimatingBubble() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateBubbleInForStashed(bubble)
+ }
+
+ // let the animation start and wait for it to complete
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ assertThat(handle.alpha).isEqualTo(0)
+ assertThat(handle.translationY)
+ .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR)
+ assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
+ assertThat(bubbleBarView.scaleX).isEqualTo(1)
+ assertThat(bubbleBarView.scaleY).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+
+ verify(bubbleStashController, atLeastOnce()).updateTaskbarTouchRegion()
+
+ // verify the hide bubble animation is pending
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+
+ animator.onBubbleBarTouchedWhileAnimating()
+
+ assertThat(animatorScheduler.delayedBlock).isNull()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ }
+
+ @Test
+ fun animateBubbleInForStashed_touchTaskbarArea_whileShowing() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateBubbleInForStashed(bubble)
+ }
+
+ // wait for the animation to start
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }
+
+ handleAnimator.assertIsRunning()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+ // verify the hide bubble animation is pending
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.onStashStateChangingWhileAnimating()
+ }
+
+ // verify that the hide animation was canceled
+ assertThat(animatorScheduler.delayedBlock).isNull()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())
+
+ // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
+ // again
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ handleAnimator.assertIsNotRunning()
+ }
+
+ @Test
+ fun animateBubbleInForStashed_touchTaskbarArea_whileHiding() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateBubbleInForStashed(bubble)
+ }
+
+ // let the animation start and wait for it to complete
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ // execute the hide bubble animation
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+ // wait for the hide animation to start
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ handleAnimator.assertIsRunning()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.onStashStateChangingWhileAnimating()
+ }
+
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())
+
+ // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
+ // again
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ handleAnimator.assertIsNotRunning()
+ }
+
+ @Test
+ fun animateBubbleInForStashed_showAnimationCanceled() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateBubbleInForStashed(bubble)
+ }
+
+ // wait for the animation to start
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }
+
+ handleAnimator.assertIsRunning()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+
+ handleAnimator.cancel()
+ handleAnimator.assertIsNotRunning()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ assertThat(animatorScheduler.delayedBlock).isNull()
+ }
+
+ @Test
+ fun animateToInitialState_inApp() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+ whenever(bubbleStashController.bubbleBarTranslationY)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateToInitialState(bubble, isInApp = true, isExpanding = false)
+ }
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ barAnimator.assertIsNotRunning()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ assertThat(bubbleBarView.alpha).isEqualTo(0)
+ assertThat(handle.translationY).isEqualTo(0)
+ assertThat(handle.alpha).isEqualTo(1)
+
+ verify(bubbleStashController).stashBubbleBarImmediate()
+ }
+
+ @Test
+ fun animateToInitialState_inApp_autoExpanding() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+ whenever(bubbleStashController.bubbleBarTranslationY)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateToInitialState(bubble, isInApp = true, isExpanding = true)
+ }
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ barAnimator.assertIsNotRunning()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ verify(bubbleStashController).showBubbleBarImmediate()
+ }
+
+ @Test
+ fun animateToInitialState_inHome() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+ whenever(bubbleStashController.bubbleBarTranslationY)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+
+ val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateToInitialState(bubble, isInApp = false, isExpanding = false)
+ }
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ barAnimator.assertIsNotRunning()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+
+ verify(bubbleStashController).showBubbleBarImmediate()
+ }
+
+ private fun setUpBubbleBar() {
+ bubbleBarView = BubbleBarView(context)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
+ val inflater = LayoutInflater.from(context)
+
+ val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
+ overflowView =
+ inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
+ overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
+ bubbleBarView.addView(overflowView)
+
+ val bubbleInfo =
+ BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false)
+ bubbleView =
+ inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
+ bubble =
+ BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
+ bubbleView.setBubble(bubble)
+ bubbleBarView.addView(bubbleView)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ }
+
+ private fun setUpBubbleStashController() {
+ bubbleStashController = mock<BubbleStashController>()
+ whenever(bubbleStashController.isStashed).thenReturn(true)
+ whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
+ .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
+ whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
+ .thenReturn(HANDLE_TRANSLATION)
+ whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+ }
+
+ private fun <T> PhysicsAnimator<T>.assertIsRunning() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ assertThat(isRunning()).isTrue()
+ }
+ }
+
+ private fun <T> PhysicsAnimator<T>.assertIsNotRunning() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ assertThat(isRunning()).isFalse()
+ }
+ }
+
+ private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler {
+
+ var delayedBlock: Runnable? = null
+ private set
+
+ override fun post(block: Runnable) {
+ block.run()
+ }
+
+ override fun postDelayed(delayMillis: Long, block: Runnable) {
+ check(delayedBlock == null) { "there is already a pending block waiting to run" }
+ delayedBlock = block
+ }
+
+ override fun cancel(block: Runnable) {
+ check(delayedBlock == block) { "the pending block does not match the canceled block" }
+ delayedBlock = null
+ }
+ }
+}
+
+private const val DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS = -20f
+private const val HANDLE_TRANSLATION = -30f
+private const val BAR_TRANSLATION_Y_FOR_TASKBAR = -50f
+private const val BAR_TRANSLATION_Y_FOR_HOTSEAT = -40f
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
similarity index 96%
rename from quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
index c327166..c8f7946 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -17,8 +17,6 @@
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_CONTEXTUAL_BUTTONS
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_NAV_BUTTONS
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_START_CONTEXTUAL_BUTTONS
-import com.android.systemui.shared.rotation.RotationButton
-import java.lang.IllegalStateException
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
@@ -39,10 +37,9 @@
private val mockRecentsButton: ImageView = mock()
private val mockHomeButton: ImageView = mock()
private val mockImeSwitcher: ImageView = mock()
- private val mockRotationButton: RotationButton = mock()
private val mockA11yButton: ImageView = mock()
private val mockSpace: Space = mock()
- private val mockConfiguration: Configuration = mock();
+ private val mockConfiguration: Configuration = mock()
private var surfaceRotation = Surface.ROTATION_0
@@ -210,7 +207,6 @@
phoneMode = phoneMode,
surfaceRotation = surfaceRotation,
imeSwitcher = mockImeSwitcher,
- rotationButton = mockRotationButton,
a11yButton = mockA11yButton,
space = mockSpace,
)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
new file mode 100644
index 0000000..eebd8f9
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.overlay
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.view.MotionEvent
+import androidx.test.annotation.UiThreadTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.AbstractFloatingView.TYPE_OPTIONS_POPUP
+import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
+import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY
+import com.android.launcher3.AbstractFloatingView.hasOpenView
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.launcher3.views.BaseDragLayer
+import com.android.systemui.shared.system.TaskStackChangeListeners
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023"])
+class TaskbarOverlayControllerTest {
+
+ @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule()
+ @InjectController lateinit var overlayController: TaskbarOverlayController
+
+ private val taskbarContext: TaskbarActivityContext
+ get() = taskbarUnitTestRule.activityContext
+
+ @Test
+ @UiThreadTest
+ fun testRequestWindow_twice_reusesWindow() {
+ val context1 = overlayController.requestWindow()
+ val context2 = overlayController.requestWindow()
+ assertThat(context1).isSameInstanceAs(context2)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() {
+ val context1 = overlayController.requestWindow()
+ overlayController.hideWindow()
+
+ val context2 = overlayController.requestWindow()
+ assertThat(context1).isNotSameInstanceAs(context2)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testRequestWindow_afterHidingOverlay_createsNewWindow() {
+ val context1 = overlayController.requestWindow()
+ TestOverlayView.show(context1)
+ overlayController.hideWindow()
+
+ val context2 = overlayController.requestWindow()
+ assertThat(context1).isNotSameInstanceAs(context2)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testRequestWindow_addsProxyView() {
+ TestOverlayView.show(overlayController.requestWindow())
+ assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
+ }
+
+ @Test
+ @UiThreadTest
+ fun testRequestWindow_closeProxyView_closesOverlay() {
+ val overlay = TestOverlayView.show(overlayController.requestWindow())
+ AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)
+ assertThat(overlay.isOpen).isFalse()
+ }
+
+ @Test
+ fun testRequestWindow_attachesDragLayer() {
+ lateinit var dragLayer: BaseDragLayer<*>
+ getInstrumentation().runOnMainSync {
+ dragLayer = overlayController.requestWindow().dragLayer
+ }
+
+ // Allow drag layer to attach before checking.
+ getInstrumentation().runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() }
+ }
+
+ @Test
+ @UiThreadTest
+ fun testHideWindow_closesOverlay() {
+ val overlay = TestOverlayView.show(overlayController.requestWindow())
+ overlayController.hideWindow()
+ assertThat(overlay.isOpen).isFalse()
+ }
+
+ @Test
+ fun testHideWindow_detachesDragLayer() {
+ lateinit var dragLayer: BaseDragLayer<*>
+ getInstrumentation().runOnMainSync {
+ dragLayer = overlayController.requestWindow().dragLayer
+ }
+
+ // Wait for drag layer to be attached to window before hiding.
+ getInstrumentation().runOnMainSync {
+ overlayController.hideWindow()
+ assertThat(dragLayer.isAttachedToWindow).isFalse()
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ fun testTwoOverlays_closeOne_windowStaysOpen() {
+ val context = overlayController.requestWindow()
+ val overlay1 = TestOverlayView.show(context)
+ val overlay2 = TestOverlayView.show(context)
+
+ overlay1.close(false)
+ assertThat(overlay2.isOpen).isTrue()
+ assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
+ }
+
+ @Test
+ @UiThreadTest
+ fun testTwoOverlays_closeAll_closesWindow() {
+ val context = overlayController.requestWindow()
+ val overlay1 = TestOverlayView.show(context)
+ val overlay2 = TestOverlayView.show(context)
+
+ overlay1.close(false)
+ overlay2.close(false)
+ assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
+ }
+
+ @Test
+ @UiThreadTest
+ fun testRecreateTaskbar_closesWindow() {
+ TestOverlayView.show(overlayController.requestWindow())
+ taskbarUnitTestRule.recreateTaskbar()
+ assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
+ }
+
+ @Test
+ fun testTaskMovedToFront_closesOverlay() {
+ lateinit var overlay: TestOverlayView
+ getInstrumentation().runOnMainSync {
+ overlay = TestOverlayView.show(overlayController.requestWindow())
+ }
+
+ TaskStackChangeListeners.getInstance().listenerImpl.onTaskMovedToFront(RunningTaskInfo())
+ // Make sure TaskStackChangeListeners' Handler posts the callback before checking state.
+ getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
+ }
+
+ @Test
+ fun testTaskStackChanged_allAppsClosed_overlayStaysOpen() {
+ lateinit var overlay: TestOverlayView
+ getInstrumentation().runOnMainSync {
+ overlay = TestOverlayView.show(overlayController.requestWindow())
+ taskbarContext.controllers.sharedState?.allAppsVisible = false
+ }
+
+ TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged()
+ getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isTrue() }
+ }
+
+ @Test
+ fun testTaskStackChanged_allAppsOpen_closesOverlay() {
+ lateinit var overlay: TestOverlayView
+ getInstrumentation().runOnMainSync {
+ overlay = TestOverlayView.show(overlayController.requestWindow())
+ taskbarContext.controllers.sharedState?.allAppsVisible = true
+ }
+
+ TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged()
+ getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
+ }
+
+ @Test
+ @UiThreadTest
+ fun testUpdateLauncherDeviceProfile_overlayNotRebindSafe_closesOverlay() {
+ val overlayContext = overlayController.requestWindow()
+ val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_OPTIONS_POPUP }
+
+ overlayController.updateLauncherDeviceProfile(
+ overlayController.launcherDeviceProfile
+ .toBuilder(overlayContext)
+ .setGestureMode(false)
+ .build()
+ )
+
+ assertThat(overlay.isOpen).isFalse()
+ }
+
+ @Test
+ @UiThreadTest
+ fun testUpdateLauncherDeviceProfile_overlayRebindSafe_overlayStaysOpen() {
+ val overlayContext = overlayController.requestWindow()
+ val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_TASKBAR_ALL_APPS }
+
+ overlayController.updateLauncherDeviceProfile(
+ overlayController.launcherDeviceProfile
+ .toBuilder(overlayContext)
+ .setGestureMode(false)
+ .build()
+ )
+
+ assertThat(overlay.isOpen).isTrue()
+ }
+
+ private class TestOverlayView
+ private constructor(
+ private val overlayContext: TaskbarOverlayContext,
+ ) : AbstractFloatingView(overlayContext, null) {
+
+ var type = TYPE_OPTIONS_POPUP
+
+ private fun show() {
+ mIsOpen = true
+ overlayContext.dragLayer.addView(this)
+ }
+
+ override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean = false
+
+ override fun handleClose(animate: Boolean) = overlayContext.dragLayer.removeView(this)
+
+ override fun isOfType(type: Int): Boolean = (type and this.type) != 0
+
+ companion object {
+ /** Adds a generic View to the Overlay window for testing. */
+ fun show(context: TaskbarOverlayContext): TestOverlayView {
+ return TestOverlayView(context).apply { show() }
+ }
+ }
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
similarity index 88%
rename from quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index db06b6b..5d62a4c 100644
--- a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -53,7 +53,7 @@
)
val expectedRadius = TaskCornerRadius.get(context)
- assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
}
@Test
@@ -67,7 +67,7 @@
)
val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
- assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
}
@Test
@@ -81,7 +81,7 @@
)
val expectedRadius = TaskCornerRadius.get(context)
- assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
}
@Test
@@ -95,7 +95,7 @@
)
val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
- assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
}
@Test
@@ -117,7 +117,7 @@
/* parentScale= */ 1.0f,
/* taskViewScale= */ 1.0f
)
- assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display1TaskRadius)
+ assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display1TaskRadius)
spyParams.updateCornerRadius(display2Context)
spyParams.setProgress(
@@ -125,7 +125,7 @@
/* parentScale= */ 1.0f,
/* taskViewScale= */ 1.0f
)
- assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display2TaskRadius)
+ assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display2TaskRadius)
}
@Test
@@ -147,7 +147,7 @@
/* parentScale= */ 1.0f,
/* taskViewScale= */ 1.0f
)
- assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display1WindowRadius)
+ assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display1WindowRadius)
spyParams.updateCornerRadius(display2Context)
spyParams.setProgress(
@@ -155,6 +155,6 @@
/* parentScale= */ 1.0f,
/* taskViewScale= */ 1.0f,
)
- assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display2WindowRadius)
+ assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display2WindowRadius)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
similarity index 78%
rename from quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
index b1ba4c6..a388510 100644
--- a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
@@ -42,6 +42,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(510)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(70)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1445)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(150)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(580)
@@ -64,6 +66,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(510)
assertThat(dp.numShownHotseatIcons).isEqualTo(4)
assertThat(dp.hotseatBorderSpace).isEqualTo(40)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1080)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(150)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(550)
@@ -85,6 +89,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(705)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(54)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1468)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(231)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(759)
@@ -100,22 +106,21 @@
@Test
fun nav_buttons_dont_interfere_with_required_hotseat_width() {
initializeVarsForTablet(isGestureMode = false, isLandscape = true)
- inv?.apply {
- hotseatColumnSpan = IntArray(4) { 4 }
- inlineQsb = BooleanArray(4) { false }
- }
+ inv?.apply { inlineQsb = BooleanArray(4) { false } }
val dp = newDP()
dp.isTaskbarPresentInApps = true
assertThat(dp.hotseatBarEndOffset).isEqualTo(660)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(100)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1975)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(300)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(1040)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1223)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1965)
}
/** This is a case when after setting the hotseat, the QSB width needs to be changed to fit */
@@ -128,13 +133,15 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(660)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
- assertThat(dp.hotseatBorderSpace).isEqualTo(36)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(34)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(4)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1179)
- assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(864)
- assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(696)
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(876)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(694)
assertThat(dp.isQsbInline).isTrue()
- assertThat(dp.hotseatQsbWidth).isEqualTo(528)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(542)
}
/**
@@ -151,6 +158,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(660)
assertThat(dp.numShownHotseatIcons).isEqualTo(5)
assertThat(dp.hotseatBorderSpace).isEqualTo(36)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(4)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1095)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(816)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(700)
@@ -158,4 +167,23 @@
assertThat(dp.isQsbInline).isTrue()
assertThat(dp.hotseatQsbWidth).isEqualTo(480)
}
+
+ @Test
+ fun increase_span_when_space_between_icons_is_less_than_minimum() {
+ initializeVarsForTwoPanel(isGestureMode = false, isLandscape = false, rows = 5, cols = 5)
+ val dp = newDP()
+ dp.isTaskbarPresentInApps = true
+
+ assertThat(dp.hotseatBarEndOffset).isEqualTo(600)
+ assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(48)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(8)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1383)
+
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(126)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(652)
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1372)
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/NavigationBarRotationContextTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/NavigationBarRotationContextTest.java
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RobolectricTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RobolectricTest.kt
new file mode 100644
index 0000000..0694aec
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RobolectricTest.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+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 RobolectricTest {
+ @Test
+ fun test1() {
+ val actual = 1 + 1
+ assertThat(actual).isEqualTo(2)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
new file mode 100644
index 0000000..ea52842
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.orientation
+
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.Gravity
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Flags
+import com.android.quickstep.orientation.LandscapePagedViewHandler.SplitIconPositions
+import com.android.quickstep.views.IconAppChipView
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when`
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class LandscapePagedViewHandlerTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val sut = LandscapePagedViewHandler()
+
+ private fun enableGridOnlyOverview(isEnabled: Boolean) {
+ if (isEnabled) {
+ setFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW,
+ Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU
+ )
+ } else {
+ setFlagsRule.disableFlags(
+ Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW,
+ Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU
+ )
+ }
+ }
+
+ /** [ Test getSplitIconsPosition ] */
+ private fun getSplitIconsPosition(isRTL: Boolean): SplitIconPositions {
+ return sut.getSplitIconsPosition(
+ TASK_ICON_HEIGHT_PX,
+ PRIMARY_SNAPSHOT,
+ TOTAL_THUMBNAIL_HEIGHT,
+ isRTL,
+ OVERVIEW_TASK_MARGIN_PX,
+ DIVIDER_SIZE_PX,
+ )
+ }
+
+ @Test
+ fun testIcon_getSplitIconsPositions() {
+ enableGridOnlyOverview(false)
+
+ val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false)
+
+ // Top-Left icon should be at the end of the primary snapshot height
+ assertThat(topLeftY).isEqualTo(250)
+ // Bottom-Right icon should be at the end of the primary height + divider + icon size
+ assertThat(bottomRightY).isEqualTo(374)
+ }
+
+ @Test
+ fun testIcon_getSplitIconsPositions_isRTL() {
+ enableGridOnlyOverview(false)
+
+ val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true)
+
+ // Top-Left icon should be at the end of the primary snapshot height
+ assertThat(topLeftY).isEqualTo(250)
+ // Bottom-Right icon should be at the end of the primary height + divider + icon size
+ assertThat(bottomRightY).isEqualTo(374)
+ }
+
+ @Test
+ fun testChip_getSplitIconsPositions() {
+ enableGridOnlyOverview(true)
+
+ val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false)
+
+ // Top-Left app chip should always be at the initial position of the first snapshot
+ assertThat(topLeftY).isEqualTo(0)
+ // Bottom-Right app chip should be at the end of the primary height + divider
+ assertThat(bottomRightY).isEqualTo(266)
+ }
+
+ @Test
+ fun testChip_getSplitIconsPositions_isRTL() {
+ enableGridOnlyOverview(true)
+
+ val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true)
+
+ // TODO(b/326377497): When started in fake seascape and rotated to landscape,
+ // the icon chips are in RTL and wrongly positioned at the right side of the snapshot.
+ // Top-Left app chip should be placed at the top left of the first snapshot, but because
+ // this issue, it's displayed at the top-right of the second snapshot.
+ // The Bottom-Right app chip is displayed at the top-right of the first snapshot because
+ // of this issue.
+ assertThat(topLeftY).isEqualTo(0)
+ assertThat(bottomRightY).isEqualTo(-316)
+ }
+
+ /** Test updateSplitIconsPosition */
+ @Test
+ fun testIcon_updateSplitIconsPosition() {
+ enableGridOnlyOverview(false)
+
+ val expectedTranslationY = 250
+ val expectedGravity = Gravity.TOP or Gravity.RIGHT
+
+ val iconView = mock<View>()
+ val frameLayout = FrameLayout.LayoutParams(100, 100)
+ `when`(iconView.layoutParams).thenReturn(frameLayout)
+
+ sut.updateSplitIconsPosition(iconView, expectedTranslationY, false)
+ assertThat(frameLayout.gravity).isEqualTo(expectedGravity)
+ assertThat(frameLayout.topMargin).isEqualTo(expectedTranslationY)
+ verify(iconView).translationX = 0f
+ verify(iconView).translationY = 0f
+ }
+
+ @Test
+ fun testIcon_updateSplitIconsPosition_isRTL() {
+ enableGridOnlyOverview(false)
+
+ val expectedTranslationY = 250
+ val expectedGravity = Gravity.TOP or Gravity.RIGHT
+
+ val iconView = mock<View>()
+ val frameLayout = FrameLayout.LayoutParams(100, 100)
+ `when`(iconView.layoutParams).thenReturn(frameLayout)
+
+ sut.updateSplitIconsPosition(iconView, expectedTranslationY, true)
+ assertThat(frameLayout.gravity).isEqualTo(expectedGravity)
+ assertThat(frameLayout.topMargin).isEqualTo(expectedTranslationY)
+ verify(iconView).translationX = 0f
+ verify(iconView).translationY = 0f
+ }
+
+ @Test
+ fun testChip_updateSplitIconsPosition() {
+ enableGridOnlyOverview(true)
+
+ val expectedTranslationY = 250
+ val frameLayout = FrameLayout.LayoutParams(100, 100)
+ val iconView = mock<IconAppChipView>()
+ `when`(iconView.layoutParams).thenReturn(frameLayout)
+
+ sut.updateSplitIconsPosition(iconView, expectedTranslationY, false)
+ assertThat(frameLayout.gravity).isEqualTo(Gravity.TOP or Gravity.END)
+ verify(iconView).setSplitTranslationX(0f)
+ verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat())
+ }
+
+ @Test
+ fun testChip_updateSplitIconsPosition_isRTL() {
+ enableGridOnlyOverview(true)
+
+ val expectedTranslationY = 250
+ val frameLayout = FrameLayout.LayoutParams(100, 100)
+ val iconView = mock<IconAppChipView>()
+ `when`(iconView.layoutParams).thenReturn(frameLayout)
+
+ sut.updateSplitIconsPosition(iconView, expectedTranslationY, true)
+ assertThat(frameLayout.gravity).isEqualTo(Gravity.BOTTOM or Gravity.START)
+ verify(iconView).setSplitTranslationX(0f)
+ verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat())
+ }
+
+ private companion object {
+ const val TASK_ICON_HEIGHT_PX = 108
+ const val OVERVIEW_TASK_MARGIN_PX = 0
+ const val DIVIDER_SIZE_PX = 16
+ const val PRIMARY_SNAPSHOT = 250
+ const val SECONDARY_SNAPSHOT = 300
+ const val TOTAL_THUMBNAIL_HEIGHT = PRIMARY_SNAPSHOT + SECONDARY_SNAPSHOT + DIVIDER_SIZE_PX
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
new file mode 100644
index 0000000..2bc182c
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
@@ -0,0 +1,197 @@
+/*
+ * 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.orientation
+
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.Gravity
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Flags
+import com.android.quickstep.orientation.LandscapePagedViewHandler.SplitIconPositions
+import com.android.quickstep.views.IconAppChipView
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when`
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class SeascapePagedViewHandlerTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val sut = SeascapePagedViewHandler()
+
+ private fun enableGridOnlyOverview(isEnabled: Boolean) {
+ if (isEnabled) {
+ setFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW,
+ Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU
+ )
+ } else {
+ setFlagsRule.disableFlags(
+ Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW,
+ Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU
+ )
+ }
+ }
+
+ /** [ Test getSplitIconsPosition ] */
+ private fun getSplitIconsPosition(isRTL: Boolean): SplitIconPositions {
+ return sut.getSplitIconsPosition(
+ TASK_ICON_HEIGHT_PX,
+ PRIMARY_SNAPSHOT,
+ TOTAL_THUMBNAIL_HEIGHT,
+ isRTL,
+ OVERVIEW_TASK_MARGIN_PX,
+ DIVIDER_SIZE_PX,
+ )
+ }
+
+ @Test
+ fun testIcon_getSplitIconsPositions() {
+ enableGridOnlyOverview(false)
+
+ val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false)
+
+ // The top-left icon is translated from the bottom of the screen to the end of
+ // the primary snapshot minus the icon size.
+ assertThat(topLeftY).isEqualTo(142)
+ // The bottom-right icon is placed at the end of the primary snapshot plus the divider.
+ assertThat(bottomRightY).isEqualTo(266)
+ }
+
+ @Test
+ fun testIcon_getSplitIconsPositions_isRTL() {
+ enableGridOnlyOverview(false)
+
+ val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true)
+
+ // The top-left icon is translated from the bottom of the screen to the end of
+ // the primary snapshot minus the icon size.
+ assertThat(topLeftY).isEqualTo(142)
+ // The bottom-right icon is placed at the end of the primary snapshot plus the divider.
+ assertThat(bottomRightY).isEqualTo(266)
+ }
+
+ @Test
+ fun testChip_getSplitIconsPositions() {
+ enableGridOnlyOverview(true)
+
+ val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false)
+
+ // Top-Left app chip should always be at the initial position of the first snapshot
+ assertThat(topLeftY).isEqualTo(0)
+ // Bottom-Right app chip should be at the end of the primary height + divider
+ assertThat(bottomRightY).isEqualTo(-266)
+ }
+
+ @Test
+ fun testChip_getSplitIconsPositions_isRTL() {
+ enableGridOnlyOverview(true)
+
+ val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true)
+
+ // TODO(b/326377497): When started in fake seascape and rotated to landscape,
+ // the icon chips are in RTL and wrongly positioned at the right side of the snapshot.
+ // Top-Left app chip should be placed at the top left of the first snapshot, but because
+ // this issue, it's displayed at the top-right of the second snapshot.
+ // The Bottom-Right app chip is displayed at the top-right of the first snapshot because
+ // of this issue.
+ assertThat(topLeftY).isEqualTo(316)
+ assertThat(bottomRightY).isEqualTo(0)
+ }
+
+ /** Test updateSplitIconsPosition */
+ @Test
+ fun testIcon_updateSplitIconsPosition() {
+ enableGridOnlyOverview(false)
+
+ val expectedTranslationY = 250
+ val expectedGravity = Gravity.BOTTOM or Gravity.LEFT
+
+ val iconView = mock<View>()
+ val frameLayout = FrameLayout.LayoutParams(100, 100)
+ `when`(iconView.layoutParams).thenReturn(frameLayout)
+
+ sut.updateSplitIconsPosition(iconView, expectedTranslationY, false)
+ assertThat(frameLayout.gravity).isEqualTo(expectedGravity)
+ assertThat(frameLayout.bottomMargin).isEqualTo(expectedTranslationY)
+ verify(iconView).translationX = 0f
+ verify(iconView).translationY = 0f
+ }
+
+ @Test
+ fun testIcon_updateSplitIconsPosition_isRTL() {
+ enableGridOnlyOverview(false)
+
+ val expectedTranslationY = 250
+ val expectedGravity = Gravity.BOTTOM or Gravity.LEFT
+
+ val iconView = mock<View>()
+ val frameLayout = FrameLayout.LayoutParams(100, 100)
+ `when`(iconView.layoutParams).thenReturn(frameLayout)
+
+ sut.updateSplitIconsPosition(iconView, expectedTranslationY, true)
+ assertThat(frameLayout.gravity).isEqualTo(expectedGravity)
+ assertThat(frameLayout.bottomMargin).isEqualTo(expectedTranslationY)
+ verify(iconView).translationX = 0f
+ verify(iconView).translationY = 0f
+ }
+
+ @Test
+ fun testChip_updateSplitIconsPosition() {
+ enableGridOnlyOverview(true)
+
+ val expectedTranslationY = 250
+ val frameLayout = FrameLayout.LayoutParams(100, 100)
+ val iconView = mock<IconAppChipView>()
+ `when`(iconView.layoutParams).thenReturn(frameLayout)
+
+ sut.updateSplitIconsPosition(iconView, expectedTranslationY, false)
+ assertThat(frameLayout.gravity).isEqualTo(Gravity.BOTTOM or Gravity.START)
+ verify(iconView).setSplitTranslationX(0f)
+ verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat())
+ }
+
+ @Test
+ fun testChip_updateSplitIconsPosition_isRTL() {
+ enableGridOnlyOverview(true)
+
+ val expectedTranslationY = 250
+ val frameLayout = FrameLayout.LayoutParams(100, 100)
+ val iconView = mock<IconAppChipView>()
+ `when`(iconView.layoutParams).thenReturn(frameLayout)
+
+ sut.updateSplitIconsPosition(iconView, expectedTranslationY, true)
+ assertThat(frameLayout.gravity).isEqualTo(Gravity.TOP or Gravity.END)
+ verify(iconView).setSplitTranslationX(0f)
+ verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat())
+ }
+
+ private companion object {
+ const val TASK_ICON_HEIGHT_PX = 108
+ const val OVERVIEW_TASK_MARGIN_PX = 0
+ const val DIVIDER_SIZE_PX = 16
+ const val PRIMARY_SNAPSHOT = 250
+ const val SECONDARY_SNAPSHOT = 300
+ const val TOTAL_THUMBNAIL_HEIGHT = PRIMARY_SNAPSHOT + SECONDARY_SNAPSHOT + DIVIDER_SIZE_PX
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt
new file mode 100644
index 0000000..eaeb513
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import com.android.quickstep.util.GroupTask
+import java.util.function.Consumer
+
+class FakeRecentTasksDataSource : RecentTasksDataSource {
+ var taskList: List<GroupTask> = listOf()
+
+ override fun getTasks(callback: Consumer<List<GroupTask>>?): Int {
+ callback?.accept(taskList)
+ return 0
+ }
+
+ fun seedTasks(tasks: List<GroupTask>) {
+ taskList = tasks
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
new file mode 100644
index 0000000..b66b735
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import android.graphics.Bitmap
+import com.android.launcher3.util.CancellableTask
+import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import java.util.function.Consumer
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+class FakeTaskThumbnailDataSource : TaskThumbnailDataSource {
+
+ val taskIdToBitmap: Map<Int, Bitmap> = (0..10).associateWith { mock() }
+ val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
+ var shouldLoadSynchronously: Boolean = true
+
+ /** Retrieves and sets a thumbnail on [task] from [taskIdToBitmap]. */
+ override fun updateThumbnailInBackground(
+ task: Task,
+ callback: Consumer<ThumbnailData>
+ ): CancellableTask<ThumbnailData>? {
+ val thumbnailData = mock<ThumbnailData>()
+ whenever(thumbnailData.thumbnail).thenReturn(taskIdToBitmap[task.key.id])
+ val wrappedCallback = {
+ task.thumbnail = thumbnailData
+ callback.accept(thumbnailData)
+ }
+ if (shouldLoadSynchronously) {
+ wrappedCallback()
+ } else {
+ taskIdToUpdatingTask[task.key.id] = wrappedCallback
+ }
+ return null
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
new file mode 100644
index 0000000..e160627
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+
+class FakeTasksRepository : RecentTasksRepository {
+ private var thumbnailDataMap: Map<Int, ThumbnailData> = emptyMap()
+ private var tasks: MutableStateFlow<List<Task>> = MutableStateFlow(emptyList())
+ private var visibleTasks: MutableStateFlow<List<Int>> = MutableStateFlow(emptyList())
+
+ override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> = tasks
+
+ override fun getTaskDataById(taskId: Int): Flow<Task?> =
+ getAllTaskData().map { taskList -> taskList.firstOrNull { it.key.id == taskId } }
+
+ override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
+ visibleTasks.value = visibleTaskIdList
+ tasks.value = tasks.value.map { it.apply { thumbnail = thumbnailDataMap[it.key.id] } }
+ }
+
+ fun seedTasks(tasks: List<Task>) {
+ this.tasks.value = tasks
+ }
+
+ fun seedThumbnailData(thumbnailDataMap: Map<Int, ThumbnailData>) {
+ this.thumbnailDataMap = thumbnailDataMap
+ }
+}
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
new file mode 100644
index 0000000..c28a85a
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import android.content.ComponentName
+import android.content.Intent
+import com.android.quickstep.TaskIconCache
+import com.android.quickstep.util.DesktopTask
+import com.android.quickstep.util.GroupTask
+import com.android.systemui.shared.recents.model.Task
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.kotlin.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class TasksRepositoryTest {
+ private val tasks = (0..5).map(::createTaskWithId)
+ private val defaultTaskList =
+ listOf(
+ GroupTask(tasks[0]),
+ GroupTask(tasks[1], tasks[2], null),
+ DesktopTask(tasks.subList(3, 6))
+ )
+ private val recentsModel = FakeRecentTasksDataSource()
+ private val taskThumbnailDataSource = FakeTaskThumbnailDataSource()
+ private val taskIconCache = mock<TaskIconCache>()
+
+ private val systemUnderTest =
+ TasksRepository(recentsModel, taskThumbnailDataSource, taskIconCache)
+
+ @Test
+ fun getAllTaskDataReturnsFlattenedListOfTasks() = runTest {
+ recentsModel.seedTasks(defaultTaskList)
+
+ assertThat(systemUnderTest.getAllTaskData(forceRefresh = true).first()).isEqualTo(tasks)
+ }
+
+ @Test
+ fun getTaskDataByIdReturnsSpecificTask() = runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ assertThat(systemUnderTest.getTaskDataById(2).first()).isEqualTo(tasks[2])
+ }
+
+ @Test
+ fun setVisibleTasksPopulatesThumbnails() = runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+ val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(listOf(1, 2))
+
+ // .drop(1) to ignore initial null content before from thumbnail was loaded.
+ assertThat(systemUnderTest.getTaskDataById(1).drop(1).first()!!.thumbnail!!.thumbnail)
+ .isEqualTo(bitmap1)
+ assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail)
+ .isEqualTo(bitmap2)
+ }
+
+ @Test
+ fun changingVisibleTasksContainsAlreadyPopulatedThumbnails() = runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(listOf(1, 2))
+
+ // .drop(1) to ignore initial null content before from thumbnail was loaded.
+ assertThat(systemUnderTest.getTaskDataById(2).drop(1).first()!!.thumbnail!!.thumbnail)
+ .isEqualTo(bitmap2)
+
+ // Prevent new loading of Bitmaps
+ taskThumbnailDataSource.shouldLoadSynchronously = false
+ systemUnderTest.setVisibleTasks(listOf(2, 3))
+
+ assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail)
+ .isEqualTo(bitmap2)
+ }
+
+ @Test
+ fun retrievedThumbnailsAreDiscardedWhenTaskBecomesInvisible() = runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(listOf(1, 2))
+
+ // .drop(1) to ignore initial null content before from thumbnail was loaded.
+ assertThat(systemUnderTest.getTaskDataById(2).drop(1).first()!!.thumbnail!!.thumbnail)
+ .isEqualTo(bitmap2)
+
+ // Prevent new loading of Bitmaps
+ taskThumbnailDataSource.shouldLoadSynchronously = false
+ systemUnderTest.setVisibleTasks(listOf(0, 1))
+
+ assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail).isNull()
+ }
+
+ @Test
+ fun retrievedThumbnailsCauseEmissionOnTaskDataFlow() = runTest {
+ // Setup fakes
+ recentsModel.seedTasks(defaultTaskList)
+ val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
+ taskThumbnailDataSource.shouldLoadSynchronously = false
+
+ // Setup TasksRepository
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+ systemUnderTest.setVisibleTasks(listOf(1, 2))
+
+ // Assert there is no bitmap in first emission
+ val taskFlow = systemUnderTest.getTaskDataById(2)
+ val taskFlowValuesList = mutableListOf<Task?>()
+ backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
+ taskFlow.toList(taskFlowValuesList)
+ }
+ assertThat(taskFlowValuesList[0]!!.thumbnail).isNull()
+
+ // Simulate bitmap loading after first emission
+ taskThumbnailDataSource.taskIdToUpdatingTask.getValue(2).invoke()
+
+ // Check for second emission
+ assertThat(taskFlowValuesList[1]!!.thumbnail!!.thumbnail).isEqualTo(bitmap2)
+ }
+
+ private fun createTaskWithId(taskId: Int) =
+ Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000))
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
new file mode 100644
index 0000000..3b8754c
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.quickstep.recents.data.FakeTasksRepository
+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.Uninitialized
+import com.android.quickstep.task.viewmodel.TaskViewData
+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.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class TaskThumbnailViewModelTest {
+ private val recentsViewData = RecentsViewData()
+ private val taskViewData = TaskViewData()
+ private val tasksRepository = FakeTasksRepository()
+ private val systemUnderTest =
+ TaskThumbnailViewModel(recentsViewData, taskViewData, tasksRepository)
+
+ private val tasks = (0..5).map(::createTaskWithId)
+
+ @Test
+ fun initialStateIsUninitialized() = runTest {
+ assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized)
+ }
+
+ @Test
+ fun bindRunningTask_thenStateIs_LiveTile() = runTest {
+ tasksRepository.seedTasks(tasks)
+ val taskThumbnail = TaskThumbnail(taskId = 1, isRunning = true)
+ systemUnderTest.bind(taskThumbnail)
+
+ assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile)
+ }
+
+ @Test
+ fun setRecentsFullscreenProgress_thenProgressIsPassedThrough() = runTest {
+ recentsViewData.fullscreenProgress.value = 0.5f
+
+ assertThat(systemUnderTest.recentsFullscreenProgress.first()).isEqualTo(0.5f)
+
+ recentsViewData.fullscreenProgress.value = 0.6f
+
+ assertThat(systemUnderTest.recentsFullscreenProgress.first()).isEqualTo(0.6f)
+ }
+
+ @Test
+ fun setAncestorScales_thenScaleIsCalculated() = runTest {
+ recentsViewData.scale.value = 0.5f
+ taskViewData.scale.value = 0.6f
+
+ assertThat(systemUnderTest.inheritedScale.first()).isEqualTo(0.3f)
+ }
+
+ @Test
+ fun bindRunningTaskThenStoppedTaskWithoutThumbnail_thenStateChangesToBackgroundOnly() =
+ runTest {
+ tasksRepository.seedTasks(tasks)
+ val runningTask = TaskThumbnail(taskId = 1, isRunning = true)
+ val stoppedTask = TaskThumbnail(taskId = 2, isRunning = false)
+ systemUnderTest.bind(runningTask)
+ assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile)
+
+ systemUnderTest.bind(stoppedTask)
+ assertThat(systemUnderTest.uiState.first())
+ .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
+ }
+
+ @Test
+ fun bindStoppedTaskWithoutThumbnail_thenStateIs_BackgroundOnly_withAlphaRemoved() = runTest {
+ tasksRepository.seedTasks(tasks)
+ val stoppedTask = TaskThumbnail(taskId = 2, isRunning = false)
+
+ systemUnderTest.bind(stoppedTask)
+ assertThat(systemUnderTest.uiState.first())
+ .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
+ }
+
+ @Test
+ fun bindLockedTaskWithThumbnail_thenStateIs_BackgroundOnly() = runTest {
+ tasksRepository.seedThumbnailData(mapOf(2 to createThumbnailData()))
+ tasks[2].isLocked = true
+ tasksRepository.seedTasks(tasks)
+ val recentTask = TaskThumbnail(taskId = 2, isRunning = false)
+
+ systemUnderTest.bind(recentTask)
+ assertThat(systemUnderTest.uiState.first())
+ .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
+ }
+
+ @Test
+ fun bindStoppedTaskWithThumbnail_thenStateIs_Snapshot_withAlphaRemoved() = runTest {
+ val expectedThumbnailData = createThumbnailData()
+ tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData))
+ tasksRepository.seedTasks(tasks)
+ tasksRepository.setVisibleTasks(listOf(2))
+ val recentTask = TaskThumbnail(taskId = 2, isRunning = false)
+
+ systemUnderTest.bind(recentTask)
+ assertThat(systemUnderTest.uiState.first())
+ .isEqualTo(
+ Snapshot(
+ backgroundColor = Color.rgb(2, 2, 2),
+ bitmap = expectedThumbnailData.thumbnail!!,
+ drawnRect = Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
+ )
+ )
+ }
+
+ @Test
+ fun bindNonVisibleStoppedTask_whenMadeVisible_thenStateIsSnapshot() = runTest {
+ val expectedThumbnailData = createThumbnailData()
+ tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData))
+ tasksRepository.seedTasks(tasks)
+ val recentTask = TaskThumbnail(taskId = 2, isRunning = false)
+
+ systemUnderTest.bind(recentTask)
+ assertThat(systemUnderTest.uiState.first())
+ .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
+ tasksRepository.setVisibleTasks(listOf(2))
+ assertThat(systemUnderTest.uiState.first())
+ .isEqualTo(
+ Snapshot(
+ backgroundColor = Color.rgb(2, 2, 2),
+ bitmap = expectedThumbnailData.thumbnail!!,
+ drawnRect = Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
+ )
+ )
+ }
+
+ private fun createTaskWithId(taskId: Int) =
+ Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ colorBackground = Color.argb(taskId, taskId, taskId, taskId)
+ }
+
+ private fun createThumbnailData(): ThumbnailData {
+ val bitmap = mock<Bitmap>()
+ whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH)
+ whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT)
+
+ return ThumbnailData(thumbnail = bitmap)
+ }
+
+ companion object {
+ const val THUMBNAIL_WIDTH = 100
+ const val THUMBNAIL_HEIGHT = 200
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
similarity index 98%
rename from quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
index 510faf6..ece67af 100644
--- a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -43,6 +43,7 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
@@ -100,12 +101,11 @@
// Stub methods on appPairsController so that they return mocks
spyAppPairsController = spy(appPairsController)
whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
- whenever(spyAppPairsController.getTopTaskTracker(mockTaskbarActivityContext))
- .thenReturn(mockTopTaskTracker)
+ doReturn(mockTopTaskTracker).whenever(spyAppPairsController).topTaskTracker
whenever(mockTopTaskTracker.getCachedTopTask(any())).thenReturn(mockCachedTaskInfo)
whenever(mockTask1.getKey()).thenReturn(mockTaskKey1)
whenever(mockTask2.getKey()).thenReturn(mockTaskKey2)
- doNothing().whenever(spyAppPairsController).launchAppPair(any())
+ doNothing().whenever(spyAppPairsController).launchAppPair(any(), any())
doNothing()
.whenever(spyAppPairsController)
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
@@ -210,7 +210,7 @@
callback.accept(arrayOf(mockTask1, mockTask2))
// Verify that launchAppPair and launchToSide were never called
- verify(spyAppPairsController, never()).launchAppPair(any())
+ verify(spyAppPairsController, never()).launchAppPair(any(), any())
verify(spyAppPairsController, never())
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
}
@@ -234,7 +234,7 @@
callback.accept(arrayOf(mockTask1, mockTask2))
// Verify that launchToSide was called with the correct arguments
- verify(spyAppPairsController, never()).launchAppPair(any())
+ verify(spyAppPairsController, never()).launchAppPair(any(), any())
verify(spyAppPairsController, times(1))
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT))
}
@@ -258,7 +258,7 @@
callback.accept(arrayOf(mockTask1, mockTask2))
// Verify that launchToSide was called with the correct arguments
- verify(spyAppPairsController, never()).launchAppPair(any())
+ verify(spyAppPairsController, never()).launchAppPair(any(), any())
verify(spyAppPairsController, times(1))
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT))
}
@@ -282,7 +282,7 @@
callback.accept(arrayOf(mockTask1, mockTask2))
// Verify that launchToSide was called with the correct arguments
- verify(spyAppPairsController, never()).launchAppPair(any())
+ verify(spyAppPairsController, never()).launchAppPair(any(), any())
verify(spyAppPairsController, times(1))
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT))
}
@@ -306,7 +306,7 @@
callback.accept(arrayOf(mockTask1, mockTask2))
// Verify that launchToSide was called with the correct arguments
- verify(spyAppPairsController, never()).launchAppPair(any())
+ verify(spyAppPairsController, never()).launchAppPair(any(), any())
verify(spyAppPairsController, times(1))
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT))
}
@@ -330,7 +330,7 @@
callback.accept(arrayOf(mockTask1, mockTask2))
// Verify that launchAppPair was called
- verify(spyAppPairsController, times(1)).launchAppPair(any())
+ verify(spyAppPairsController, times(1)).launchAppPair(any(), any())
verify(spyAppPairsController, never())
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
}
@@ -354,7 +354,7 @@
callback.accept(arrayOf(mockTask1, mockTask2))
// Verify that launchToSide was called with the correct arguments
- verify(spyAppPairsController, never()).launchAppPair(any())
+ verify(spyAppPairsController, never()).launchAppPair(any(), any())
verify(spyAppPairsController, times(1))
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT))
}
@@ -378,7 +378,7 @@
callback.accept(arrayOf(mockTask1, mockTask2))
// Verify that launchToSide was called with the correct arguments
- verify(spyAppPairsController, never()).launchAppPair(any())
+ verify(spyAppPairsController, never()).launchAppPair(any(), any())
verify(spyAppPairsController, times(1))
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT))
}
@@ -402,7 +402,7 @@
callback.accept(arrayOf(mockTask1, mockTask2))
// Verify that launchAppPair was called
- verify(spyAppPairsController, times(1)).launchAppPair(any())
+ verify(spyAppPairsController, times(1)).launchAppPair(any(), any())
verify(spyAppPairsController, never())
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
}
diff --git a/quickstep/tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
similarity index 73%
rename from quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 68c9bf9..d40f8ab 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -17,6 +17,8 @@
package com.android.quickstep.util
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.ContextThemeWrapper
@@ -31,16 +33,18 @@
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.views.GroupedTaskView
import com.android.quickstep.views.IconView
-import com.android.quickstep.views.TaskThumbnailView
+import com.android.quickstep.views.TaskThumbnailViewDeprecated
import com.android.quickstep.views.TaskView
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.quickstep.views.TaskView.TaskContainer
import com.android.systemui.shared.recents.model.Task
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
import org.mockito.kotlin.any
import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
@@ -55,7 +59,7 @@
private val mockSplitSelectStateController: SplitSelectStateController = mock()
// TaskView
private val mockTaskView: TaskView = mock()
- private val mockThumbnailView: TaskThumbnailView = mock()
+ private val mockThumbnailView: TaskThumbnailViewDeprecated = mock()
private val mockBitmap: Bitmap = mock()
private val mockIconView: IconView = mock()
private val mockTaskViewDrawable: Drawable = mock()
@@ -63,7 +67,7 @@
private val mockGroupedTaskView: GroupedTaskView = mock()
private val mockTask: Task = mock()
private val mockTaskKey: Task.TaskKey = mock()
- private val mockTaskIdAttributeContainer: TaskIdAttributeContainer = mock()
+ private val mockTaskContainer: TaskContainer = mock()
// AppPairIcon
private val mockAppPairIcon: AppPairIcon = mock()
private val mockContextThemeWrapper: ContextThemeWrapper = mock()
@@ -74,19 +78,20 @@
private val mockSplitSourceDrawable: Drawable = mock()
private val mockSplitSourceView: View = mock()
- private val stateManager: StateManager<*> = mock()
+ private val stateManager: StateManager<*, *> = mock()
private val depthController: DepthController = mock()
private val transitionInfo: TransitionInfo = mock()
private val transaction: Transaction = mock()
- lateinit var splitAnimationController: SplitAnimationController
+ private lateinit var splitAnimationController: SplitAnimationController
@Before
fun setup() {
- whenever(mockTaskView.thumbnail).thenReturn(mockThumbnailView)
+ whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView)
whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap)
- whenever(mockTaskView.iconView).thenReturn(mockIconView)
+ whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
+ whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable)
whenever(splitSelectSource.view).thenReturn(mockSplitSourceView)
@@ -173,14 +178,13 @@
// Remove icon view from GroupedTaskView
whenever(mockIconView.drawable).thenReturn(null)
- whenever(mockTaskIdAttributeContainer.task).thenReturn(mockTask)
- whenever(mockTaskIdAttributeContainer.iconView).thenReturn(mockIconView)
- whenever(mockTaskIdAttributeContainer.thumbnailView).thenReturn(mockThumbnailView)
+ whenever(mockTaskContainer.task).thenReturn(mockTask)
+ whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
+ whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView)
whenever(mockTask.getKey()).thenReturn(mockTaskKey)
whenever(mockTaskKey.getId()).thenReturn(taskId)
whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)
- whenever(mockGroupedTaskView.taskIdAttributeContainers)
- .thenReturn(Array(1) { mockTaskIdAttributeContainer })
+ whenever(mockGroupedTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
splitAnimationController.getFirstAnimInitViews(
{ mockGroupedTaskView },
@@ -200,7 +204,16 @@
doNothing()
.whenever(spySplitAnimationController)
.composeRecentsSplitLaunchAnimatorLegacy(
- any(), any(), any(), any(), any(), any(), any(), any(), any())
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any()
+ )
spySplitAnimationController.playSplitLaunchAnimation(
mockGroupedTaskView,
@@ -219,7 +232,16 @@
verify(spySplitAnimationController)
.composeRecentsSplitLaunchAnimatorLegacy(
- any(), any(), any(), any(), any(), any(), any(), any(), any())
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any()
+ )
}
@Test
@@ -255,6 +277,7 @@
doNothing()
.whenever(spySplitAnimationController)
.composeIconSplitLaunchAnimator(any(), any(), any(), any())
+ doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
null /* launchingTaskView */,
@@ -276,12 +299,13 @@
}
@Test
- fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarContextCorrectly() {
+ fun playsAppropriateSplitLaunchAnimation_playsIconFullscreenLaunchCorrectly() {
val spySplitAnimationController = spy(splitAnimationController)
- whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
+ whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper)
doNothing()
.whenever(spySplitAnimationController)
- .composeScaleUpLaunchAnimation(any(), any(), any())
+ .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), any())
+ doReturn(0).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
null /* launchingTaskView */,
@@ -298,7 +322,62 @@
{} /* finishCallback */
)
- verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any())
+ verify(spySplitAnimationController)
+ .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), eq(0))
+ }
+
+ @Test
+ fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarCMultiWindow() {
+ val spySplitAnimationController = spy(splitAnimationController)
+ whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
+ doNothing()
+ .whenever(spySplitAnimationController)
+ .composeScaleUpLaunchAnimation(any(), any(), any(), any())
+ doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
+ spySplitAnimationController.playSplitLaunchAnimation(
+ null /* launchingTaskView */,
+ mockAppPairIcon,
+ taskId,
+ taskId2,
+ null /* apps */,
+ null /* wallpapers */,
+ null /* nonApps */,
+ stateManager,
+ depthController,
+ transitionInfo,
+ transaction,
+ {} /* finishCallback */
+ )
+
+ verify(spySplitAnimationController)
+ .composeScaleUpLaunchAnimation(any(), any(), any(), eq(WINDOWING_MODE_MULTI_WINDOW))
+ }
+
+ @Test
+ fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarFullscreen() {
+ val spySplitAnimationController = spy(splitAnimationController)
+ whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
+ doNothing()
+ .whenever(spySplitAnimationController)
+ .composeScaleUpLaunchAnimation(any(), any(), any(), any())
+ doReturn(0).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
+ spySplitAnimationController.playSplitLaunchAnimation(
+ null /* launchingTaskView */,
+ mockAppPairIcon,
+ taskId,
+ taskId2,
+ null /* apps */,
+ null /* wallpapers */,
+ null /* nonApps */,
+ stateManager,
+ depthController,
+ transitionInfo,
+ transaction,
+ {} /* finishCallback */
+ )
+
+ verify(spySplitAnimationController)
+ .composeScaleUpLaunchAnimation(any(), any(), any(), eq(WINDOWING_MODE_FULLSCREEN))
}
@Test
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
similarity index 94%
rename from quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 18b1ea0..bab84ef 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -36,6 +36,8 @@
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
+import com.android.quickstep.util.SplitSelectStateController.SplitFromDesktopController
+import com.android.quickstep.views.RecentsViewContainer
import com.android.systemui.shared.recents.model.Task
import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
import java.util.function.Consumer
@@ -60,11 +62,12 @@
private val depthController: DepthController = mock()
private val statsLogManager: StatsLogManager = mock()
private val statsLogger: StatsLogger = mock()
- private val stateManager: StateManager<LauncherState> = mock()
+ private val stateManager: StateManager<LauncherState, StatefulActivity<LauncherState>> = mock()
private val handler: Handler = mock()
- private val context: StatefulActivity<*> = mock()
+ private val context: RecentsViewContainer = mock()
private val recentsModel: RecentsModel = mock()
private val pendingIntent: PendingIntent = mock()
+ private val splitFromDesktopController: SplitFromDesktopController = mock()
private lateinit var splitSelectStateController: SplitSelectStateController
@@ -118,7 +121,7 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent),
false /* findExactPairMatch */,
@@ -171,7 +174,7 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
@@ -212,7 +215,7 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
@@ -268,7 +271,7 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
@@ -321,7 +324,7 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
@@ -375,7 +378,7 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent, matchingComponent),
false /* findExactPairMatch */,
@@ -428,7 +431,7 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
@@ -494,7 +497,7 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
@@ -546,7 +549,7 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent2, matchingComponent),
true /* findExactPairMatch */,
@@ -607,6 +610,18 @@
assertTrue(splitSelectStateController.isBothSplitAppsConfirmed)
}
+ @Test
+ fun splitSelectStateControllerDestroyed_SplitFromDesktopControllerAlsoDestroyed() {
+ // Initiate split from desktop controller
+ splitSelectStateController.initSplitFromDesktopController(splitFromDesktopController)
+
+ // Simulate default controller being destroyed
+ splitSelectStateController.onDestroy()
+
+ // Verify desktop controller is also destroyed
+ verify(splitFromDesktopController).onDestroy()
+ }
+
// Generate GroupTask with default userId.
private fun generateGroupTask(
task1ComponentName: ComponentName,
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskGridNavHelperTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/util/TaskGridNavHelperTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
similarity index 98%
rename from quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 9fa4b79..72cfd92 100644
--- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -44,7 +44,6 @@
import com.android.launcher3.util.window.CachedDisplayInfo;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.quickstep.FallbackActivityInterface;
-import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.SurfaceTransaction.MockProperties;
import org.hamcrest.Description;
@@ -160,7 +159,6 @@
void verifyNoTransforms() {
LauncherModelHelper helper = new LauncherModelHelper();
try {
- helper.sandboxContext.allow(SystemUiProxy.INSTANCE);
int rotation = mDisplaySize.x > mDisplaySize.y
? Surface.ROTATION_90 : Surface.ROTATION_0;
CachedDisplayInfo cdi = new CachedDisplayInfo(mDisplaySize, rotation);
diff --git a/quickstep/tests/multivalentTestsForDevice b/quickstep/tests/multivalentTestsForDevice
new file mode 120000
index 0000000..fa0fabf
--- /dev/null
+++ b/quickstep/tests/multivalentTestsForDevice
@@ -0,0 +1 @@
+./multivalentTests
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index 37dde10..7b57c81 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.model;
+import static android.content.pm.ApplicationInfo.CATEGORY_PRODUCTIVITY;
+import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
import static android.os.Process.myUserHandle;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
@@ -37,6 +39,8 @@
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
import android.os.UserHandle;
import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
@@ -81,6 +85,8 @@
private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
private LauncherModelHelper mModelHelper;
private UserHandle mUserHandle;
+ private LauncherApps mLauncherApps;
+
@Before
public void setup() throws Exception {
@@ -103,12 +109,18 @@
allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
mApp4Provider1, mApp4Provider2, mApp5Provider1);
+ mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
doAnswer(i -> {
String pkg = i.getArgument(0);
- return ApplicationInfoBuilder.newBuilder().setPackageName(pkg).setName(
- "App " + pkg).build();
- }).when(mModelHelper.sandboxContext.getPackageManager())
- .getApplicationInfo(anyString(), anyInt());
+ ApplicationInfo applicationInfo = ApplicationInfoBuilder.newBuilder()
+ .setPackageName(pkg)
+ .setName("App " + pkg)
+ .build();
+ applicationInfo.category = CATEGORY_PRODUCTIVITY;
+ applicationInfo.flags = FLAG_INSTALLED;
+ return applicationInfo;
+ }).when(mLauncherApps).getApplicationInfo(anyString(), anyInt(), any());
+
AppWidgetManager manager = mModelHelper.sandboxContext.spyService(AppWidgetManager.class);
doReturn(allWidgets).when(manager).getInstalledProviders();
doReturn(allWidgets).when(manager).getInstalledProvidersForProfile(eq(myUserHandle()));
@@ -172,7 +184,7 @@
}
@Test
- public void widgetsRecommendationRan_shouldReturnPackageWidgetsWhenEmpty() {
+ public void widgetsRecommendationRan_shouldReturnEmptyWidgetsWhenEmpty() {
runOnExecutorSync(MODEL_EXECUTOR, () -> {
// Not installed widget
@@ -192,19 +204,12 @@
newWidgetsPredicationTask(List.of(widget5, widget3, widget4, widget1)));
runOnExecutorSync(MAIN_EXECUTOR, () -> { });
- // THEN only 2 widgets are returned because the launcher only filters out
- // non-exist widgets.
+ // Only widgets suggested by prediction system are returned.
List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
.stream()
.map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
.collect(Collectors.toList());
- assertThat(recommendedWidgets).hasSize(2);
- recommendedWidgets.forEach(pendingAddWidgetInfo ->
- assertThat(pendingAddWidgetInfo.recommendationCategory).isNotNull()
- );
- // Another widget from the same package
- assertWidgetInfo(recommendedWidgets.get(0).info, mApp4Provider2);
- assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
+ assertThat(recommendedWidgets).hasSize(0);
});
}
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
new file mode 100644
index 0000000..5c7b4ab
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.app.prediction.AppTarget
+import android.app.prediction.AppTargetEvent
+import android.app.prediction.AppTargetId
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.content.Context
+import android.os.Process.myUserHandle
+import android.os.UserHandle
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.WidgetPredictionsRequester.buildBundleForPredictionSession
+import com.android.launcher3.model.WidgetPredictionsRequester.filterPredictions
+import com.android.launcher3.model.WidgetPredictionsRequester.notOnUiSurfaceFilter
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Predicate
+import junit.framework.Assert.assertNotNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+class WidgetsPredictionsRequesterTest {
+
+ private lateinit var mUserHandle: UserHandle
+ private lateinit var context: Context
+ private lateinit var deviceProfile: DeviceProfile
+ private lateinit var testInvariantProfile: InvariantDeviceProfile
+
+ private lateinit var widget1aInfo: AppWidgetProviderInfo
+ private lateinit var widget1bInfo: AppWidgetProviderInfo
+ private lateinit var widget2Info: AppWidgetProviderInfo
+
+ private lateinit var widgetItem1a: WidgetItem
+ private lateinit var widgetItem1b: WidgetItem
+ private lateinit var widgetItem2: WidgetItem
+
+ private lateinit var allWidgets: Map<PackageUserKey, List<WidgetItem>>
+
+ @Mock private lateinit var iconCache: IconCache
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mUserHandle = myUserHandle()
+ context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+ testInvariantProfile = LauncherAppState.getIDP(context)
+ deviceProfile = testInvariantProfile.getDeviceProfile(context).copy(context)
+
+ widget1aInfo =
+ createAppWidgetProviderInfo(
+ ComponentName.createRelative(APP_1_PACKAGE_NAME, APP_1_PROVIDER_A_CLASS_NAME)
+ )
+ widget1bInfo =
+ createAppWidgetProviderInfo(
+ ComponentName.createRelative(APP_1_PACKAGE_NAME, APP_1_PROVIDER_B_CLASS_NAME)
+ )
+ widgetItem1a = createWidgetItem(widget1aInfo)
+ widgetItem1b = createWidgetItem(widget1bInfo)
+
+ widget2Info =
+ createAppWidgetProviderInfo(
+ ComponentName.createRelative(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME)
+ )
+ widgetItem2 = createWidgetItem(widget2Info)
+
+ allWidgets =
+ mapOf(
+ PackageUserKey(APP_1_PACKAGE_NAME, mUserHandle) to
+ listOf(widgetItem1a, widgetItem1b),
+ PackageUserKey(APP_2_PACKAGE_NAME, mUserHandle) to listOf(widgetItem2),
+ )
+ }
+
+ @Test
+ fun buildBundleForPredictionSession_includesAddedAppWidgets() {
+ val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo, widget2Info)
+
+ val bundle = buildBundleForPredictionSession(existingWidgets, TEST_UI_SURFACE)
+ val addedWidgetsBundleExtra =
+ bundle.getParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, AppTarget::class.java)
+
+ assertNotNull(addedWidgetsBundleExtra)
+ assertThat(addedWidgetsBundleExtra)
+ .containsExactly(
+ buildExpectedAppTargetEvent(
+ /*pkg=*/ APP_1_PACKAGE_NAME,
+ /*providerClassName=*/ APP_1_PROVIDER_A_CLASS_NAME,
+ /*user=*/ mUserHandle
+ ),
+ buildExpectedAppTargetEvent(
+ /*pkg=*/ APP_1_PACKAGE_NAME,
+ /*providerClassName=*/ APP_1_PROVIDER_B_CLASS_NAME,
+ /*user=*/ mUserHandle
+ ),
+ buildExpectedAppTargetEvent(
+ /*pkg=*/ APP_2_PACKAGE_NAME,
+ /*providerClassName=*/ APP_2_PROVIDER_1_CLASS_NAME,
+ /*user=*/ mUserHandle
+ )
+ )
+ }
+
+ @Test
+ fun filterPredictions_notOnUiSurfaceFilter_returnsOnlyEligiblePredictions() {
+ val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo)
+ val filter: Predicate<WidgetItem> = notOnUiSurfaceFilter(widgetsAlreadyOnSurface)
+
+ val predictions =
+ listOf(
+ // already on surface
+ AppTarget(
+ AppTargetId(APP_1_PACKAGE_NAME),
+ APP_1_PACKAGE_NAME,
+ APP_1_PROVIDER_B_CLASS_NAME,
+ mUserHandle
+ ),
+ // eligible
+ AppTarget(
+ AppTargetId(APP_2_PACKAGE_NAME),
+ APP_2_PACKAGE_NAME,
+ APP_2_PROVIDER_1_CLASS_NAME,
+ mUserHandle
+ )
+ )
+
+ // only 2 was eligible
+ assertThat(filterPredictions(predictions, allWidgets, filter)).containsExactly(widgetItem2)
+ }
+
+ @Test
+ fun filterPredictions_appPredictions_returnsWidgetFromPackage() {
+ val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo)
+ val filter: Predicate<WidgetItem> = notOnUiSurfaceFilter(widgetsAlreadyOnSurface)
+
+ val predictions =
+ listOf(
+ AppTarget(
+ AppTargetId(APP_1_PACKAGE_NAME),
+ APP_1_PACKAGE_NAME,
+ "$APP_1_PACKAGE_NAME.SomeActivity",
+ mUserHandle
+ ),
+ AppTarget(
+ AppTargetId(APP_2_PACKAGE_NAME),
+ APP_2_PACKAGE_NAME,
+ "$APP_2_PACKAGE_NAME.SomeActivity2",
+ mUserHandle
+ ),
+ )
+
+ assertThat(filterPredictions(predictions, allWidgets, filter))
+ .containsExactly(widgetItem1a, widgetItem2)
+ }
+
+ private fun createWidgetItem(
+ providerInfo: AppWidgetProviderInfo,
+ ): WidgetItem {
+ val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo)
+ return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context)
+ }
+
+ companion object {
+ const val TEST_UI_SURFACE = "widgets_test"
+ const val BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets"
+
+ const val APP_1_PACKAGE_NAME = "com.example.app1"
+ const val APP_1_PROVIDER_A_CLASS_NAME = "app1Provider1"
+ const val APP_1_PROVIDER_B_CLASS_NAME = "app1Provider2"
+
+ const val APP_2_PACKAGE_NAME = "com.example.app2"
+ const val APP_2_PROVIDER_1_CLASS_NAME = "app2Provider1"
+
+ const val TEST_PACKAGE = "pkg"
+
+ private fun buildExpectedAppTargetEvent(
+ pkg: String,
+ providerClassName: String,
+ userHandle: UserHandle
+ ): AppTargetEvent {
+ val appTarget =
+ AppTarget.Builder(
+ /*id=*/ AppTargetId("widget:$pkg"),
+ /*packageName=*/ pkg,
+ /*user=*/ userHandle
+ )
+ .setClassName(providerClassName)
+ .build()
+ return AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
+ .setLaunchLocation(TEST_UI_SURFACE)
+ .build()
+ }
+ }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
index f3115c6..04012c0 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
@@ -37,7 +37,7 @@
lateinit var stateListener: StateManager.StateListener<RecentsState>
private val recentsActivity: RecentsActivity = mock()
- private val stateManager: StateManager<RecentsState> = mock()
+ private val stateManager: StateManager<RecentsState, RecentsActivity> = mock()
@Before
override fun setup() {
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
index ed88c29..e619e7c 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
@@ -105,7 +105,7 @@
verify(navbarButtonsViewController, times(1)).setBackForBouncer(false)
}
- private fun setFlags(flags: Int) {
+ private fun setFlags(flags: Long) {
taskbarKeyguardController.updateStateForSysuiFlags(flags)
}
}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
new file mode 100644
index 0000000..104263a
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Process
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.statehandlers.DesktopVisibilityController
+import com.android.quickstep.RecentsModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidTestingRunner::class)
+class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
+
+ @get:Rule val mockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var mockRecentsModel: RecentsModel
+ @Mock private lateinit var mockDesktopVisibilityController: DesktopVisibilityController
+
+ private var nextTaskId: Int = 500
+
+ private lateinit var recentAppsController: TaskbarRecentAppsController
+ private lateinit var userHandle: UserHandle
+
+ @Before
+ fun setUp() {
+ super.setup()
+ userHandle = Process.myUserHandle()
+ recentAppsController =
+ TaskbarRecentAppsController(mockRecentsModel) { mockDesktopVisibilityController }
+ recentAppsController.init(taskbarControllers)
+ recentAppsController.isEnabled = true
+ recentAppsController.setApps(
+ ALL_APP_PACKAGES.map { createTestAppInfo(packageName = it) }.toTypedArray()
+ )
+ }
+
+ @Test
+ fun updateHotseatItemInfos_notInDesktopMode_returnsExistingHotseatItems() {
+ setInDesktopMode(false)
+ val hotseatItems =
+ createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
+
+ assertThat(recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()))
+ .isEqualTo(hotseatItems.toTypedArray())
+ }
+
+ @Test
+ fun updateHotseatItemInfos_notInDesktopMode_runningApps_returnsExistingHotseatItems() {
+ setInDesktopMode(false)
+ val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
+ val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages)
+ val runningTasks =
+ createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
+ whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
+ recentAppsController.updateRunningApps()
+
+ val newHotseatItems =
+ recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
+
+ assertThat(newHotseatItems.map { it?.targetPackage })
+ .containsExactlyElementsIn(hotseatPackages)
+ }
+
+ @Test
+ fun updateHotseatItemInfos_noRunningApps_returnsExistingHotseatItems() {
+ setInDesktopMode(true)
+ val hotseatItems =
+ createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
+
+ assertThat(recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()))
+ .isEqualTo(hotseatItems.toTypedArray())
+ }
+
+ @Test
+ fun updateHotseatItemInfos_returnsExistingHotseatItemsAndRunningApps() {
+ setInDesktopMode(true)
+ val hotseatItems =
+ createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
+ val runningTasks =
+ createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
+ whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
+ recentAppsController.updateRunningApps()
+
+ val newHotseatItems =
+ recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
+
+ val expectedPackages =
+ listOf(
+ HOTSEAT_PACKAGE_1,
+ HOTSEAT_PACKAGE_2,
+ RUNNING_APP_PACKAGE_1,
+ RUNNING_APP_PACKAGE_2,
+ )
+ assertThat(newHotseatItems.map { it?.targetPackage })
+ .containsExactlyElementsIn(expectedPackages)
+ }
+
+ @Test
+ fun updateHotseatItemInfos_runningAppIsHotseatItem_returnsDistinctItems() {
+ setInDesktopMode(true)
+ val hotseatItems =
+ createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
+ val runningTasks =
+ createDesktopTasksFromPackageNames(
+ listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ )
+ whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
+ recentAppsController.updateRunningApps()
+
+ val newHotseatItems =
+ recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
+
+ val expectedPackages =
+ listOf(
+ HOTSEAT_PACKAGE_1,
+ HOTSEAT_PACKAGE_2,
+ RUNNING_APP_PACKAGE_1,
+ RUNNING_APP_PACKAGE_2,
+ )
+ assertThat(newHotseatItems.map { it?.targetPackage })
+ .containsExactlyElementsIn(expectedPackages)
+ }
+
+ @Test
+ fun getRunningApps_notInDesktopMode_returnsEmptySet() {
+ setInDesktopMode(false)
+ val runningTasks =
+ createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
+ whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
+ recentAppsController.updateRunningApps()
+
+ assertThat(recentAppsController.runningApps).isEmpty()
+ assertThat(recentAppsController.minimizedApps).isEmpty()
+ }
+
+ @Test
+ fun getRunningApps_inDesktopMode_returnsRunningApps() {
+ setInDesktopMode(true)
+ val runningTasks =
+ createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
+ whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
+ recentAppsController.updateRunningApps()
+
+ assertThat(recentAppsController.runningApps)
+ .containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ assertThat(recentAppsController.minimizedApps).isEmpty()
+ }
+
+ @Test
+ fun getMinimizedApps_inDesktopMode_returnsAllAppsRunningAndInvisibleAppsMinimized() {
+ setInDesktopMode(true)
+ val runningTasks =
+ ArrayList(
+ listOf(
+ createDesktopTaskInfo(RUNNING_APP_PACKAGE_1) { isVisible = true },
+ createDesktopTaskInfo(RUNNING_APP_PACKAGE_2) { isVisible = true },
+ createDesktopTaskInfo(RUNNING_APP_PACKAGE_3) { isVisible = false },
+ )
+ )
+ whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
+ recentAppsController.updateRunningApps()
+
+ assertThat(recentAppsController.runningApps)
+ .containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3)
+ assertThat(recentAppsController.minimizedApps).containsExactly(RUNNING_APP_PACKAGE_3)
+ }
+
+ private fun createHotseatItemsFromPackageNames(packageNames: List<String>): List<ItemInfo> {
+ return packageNames.map { createTestAppInfo(packageName = it) }
+ }
+
+ private fun createDesktopTasksFromPackageNames(
+ packageNames: List<String>
+ ): ArrayList<RunningTaskInfo> {
+ return ArrayList(packageNames.map { createDesktopTaskInfo(packageName = it) })
+ }
+
+ private fun createDesktopTaskInfo(
+ packageName: String,
+ init: RunningTaskInfo.() -> Unit = { isVisible = true },
+ ): RunningTaskInfo {
+ return RunningTaskInfo().apply {
+ taskId = nextTaskId++
+ configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ realActivity = ComponentName(packageName, "TestActivity")
+ init()
+ }
+ }
+
+ private fun createTestAppInfo(
+ packageName: String = "testPackageName",
+ className: String = "testClassName"
+ ) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent())
+
+ private fun setInDesktopMode(inDesktopMode: Boolean) {
+ whenever(mockDesktopVisibilityController.areDesktopTasksVisible()).thenReturn(inDesktopMode)
+ }
+
+ private companion object {
+ const val HOTSEAT_PACKAGE_1 = "hotseat1"
+ const val HOTSEAT_PACKAGE_2 = "hotseat2"
+ const val RUNNING_APP_PACKAGE_1 = "running1"
+ const val RUNNING_APP_PACKAGE_2 = "running2"
+ const val RUNNING_APP_PACKAGE_3 = "running3"
+ val ALL_APP_PACKAGES =
+ listOf(
+ HOTSEAT_PACKAGE_1,
+ HOTSEAT_PACKAGE_2,
+ RUNNING_APP_PACKAGE_1,
+ RUNNING_APP_PACKAGE_2,
+ RUNNING_APP_PACKAGE_3,
+ )
+ }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/testcomponent/ExcludeFromRecentsTestActivity.java b/quickstep/tests/src/com/android/launcher3/testcomponent/ExcludeFromRecentsTestActivity.java
new file mode 100644
index 0000000..68ac3d5
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/testcomponent/ExcludeFromRecentsTestActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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.testcomponent;
+
+/**
+ * Extension of BaseTestingActivity to help test excludeFromRecents="true".
+ */
+public class ExcludeFromRecentsTestActivity extends BaseTestingActivity {}
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index 6a48b77..44c23ba 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -23,9 +23,9 @@
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
-import com.android.launcher3.Launcher;
import com.android.launcher3.tapl.LaunchedAppState;
import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.views.RecentsView;
import org.junit.rules.RuleChain;
@@ -34,7 +34,7 @@
/**
* Base class for all instrumentation tests that deal with Quickstep.
*/
-public abstract class AbstractQuickStepTest extends AbstractLauncherUiTest {
+public abstract class AbstractQuickStepTest extends AbstractLauncherUiTest<QuickstepLauncher> {
public static final boolean ENABLE_SHELL_TRANSITIONS =
SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
@Override
@@ -46,7 +46,7 @@
}
@Override
- protected void onLauncherActivityClose(Launcher launcher) {
+ protected void onLauncherActivityClose(QuickstepLauncher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
if (recentsView != null) {
recentsView.finishRecentsAnimation(false /* toRecents */, null);
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
new file mode 100644
index 0000000..50b5df1
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.content.ComponentName
+import android.content.Intent
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.AbstractFloatingViewHelper
+import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.uioverrides.QuickstepLauncher
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.TransformingTouchDelegate
+import com.android.quickstep.TaskOverlayFactory.TaskOverlay
+import com.android.quickstep.views.LauncherRecentsView
+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.common.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.DesktopModeStatus
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+/** Test for DesktopSystemShortcut */
+class DesktopSystemShortcutTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+ private val launcher: QuickstepLauncher = mock()
+ private val statsLogManager: StatsLogManager = mock()
+ private val statsLogger: StatsLogManager.StatsLogger = mock()
+ private val recentsView: LauncherRecentsView = mock()
+ private val taskView: TaskView = mock()
+ private val workspaceItemInfo: WorkspaceItemInfo = mock()
+ private val abstractFloatingViewHelper: AbstractFloatingViewHelper = mock()
+ private val thumbnailViewDeprecated: TaskThumbnailViewDeprecated = mock()
+ private val iconView: TaskViewIcon = mock()
+ private val transformingTouchDelegate: TransformingTouchDelegate = mock()
+ private val factory: TaskShortcutFactory =
+ DesktopSystemShortcut.createFactory(abstractFloatingViewHelper)
+ private val overlayFactory: TaskOverlayFactory = mock()
+ private val overlay: TaskOverlay<*> = mock()
+
+ private lateinit var mockitoSession: StaticMockitoSession
+
+ @Before
+ fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ ExtendedMockito.doReturn(true).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
+ ExtendedMockito.doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ whenever(overlayFactory.createOverlay(any())).thenReturn(overlay)
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun createDesktopTaskShortcutFactory_desktopModeDisabled() {
+ setFlagsRule.disableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+
+ val task =
+ Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ isDockable = true
+ }
+ val taskContainer = createTaskContainer(task)
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ fun createDesktopTaskShortcutFactory_desktopModeEnabled_DeviceNotSupported() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+ val taskContainer = createTaskContainer(createTask())
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ fun createDesktopTaskShortcutFactory_desktopModeEnabled_DeviceNotSupported_OverrideEnabled() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
+
+ val taskContainer = spy(createTaskContainer(createTask()))
+ doReturn(workspaceItemInfo).whenever(taskContainer).itemInfo
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNotNull()
+ }
+
+ @Test
+ fun createDesktopTaskShortcutFactory_undockable() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+
+ val unDockableTask = createTask().apply { isDockable = false }
+ val taskContainer = createTaskContainer(unDockableTask)
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ fun desktopSystemShortcutClicked() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+
+ val task = createTask()
+ val taskContainer = spy(createTaskContainer(task))
+
+ whenever(launcher.getOverviewPanel<LauncherRecentsView>()).thenReturn(recentsView)
+ whenever(launcher.statsLogManager).thenReturn(statsLogManager)
+ whenever(statsLogManager.logger()).thenReturn(statsLogger)
+ whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger)
+ whenever(recentsView.moveTaskToDesktop(any(), any(), any())).thenAnswer {
+ val successCallback = it.getArgument<Runnable>(2)
+ successCallback.run()
+ }
+ doReturn(workspaceItemInfo).whenever(taskContainer).itemInfo
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).hasSize(1)
+ assertThat(shortcuts!!.first()).isInstanceOf(DesktopSystemShortcut::class.java)
+
+ val desktopShortcut = shortcuts.first() as DesktopSystemShortcut
+
+ desktopShortcut.onClick(taskView)
+
+ val allTypesExceptRebindSafe =
+ AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv()
+ verify(abstractFloatingViewHelper).closeOpenViews(launcher, true, allTypesExceptRebindSafe)
+ verify(recentsView)
+ .moveTaskToDesktop(
+ eq(taskContainer),
+ eq(DesktopModeTransitionSource.APP_FROM_OVERVIEW),
+ any()
+ )
+ verify(statsLogger).withItemInfo(workspaceItemInfo)
+ verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP)
+ }
+
+ private fun createTask(): Task {
+ return Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ isDockable = true
+ }
+ }
+
+ private fun createTaskContainer(task: Task): TaskView.TaskContainer {
+ return taskView.TaskContainer(
+ task,
+ thumbnailView = null,
+ thumbnailViewDeprecated,
+ iconView,
+ transformingTouchDelegate,
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+ digitalWellBeingToast = null,
+ showWindowsView = null,
+ overlayFactory
+ )
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 213f58f..2858929 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -32,8 +32,6 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -59,6 +57,7 @@
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.ExtendedLongPressTimeoutRule;
import com.android.launcher3.util.rule.FailureWatcher;
import com.android.launcher3.util.rule.SamplerRule;
import com.android.launcher3.util.rule.ScreenRecordRule;
@@ -76,17 +75,21 @@
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
+import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class FallbackRecentsTest {
private static final String FALLBACK_LAUNCHER_TITLE = "Test launcher";
+ private static final Pattern COMPONENT_INFO_REGEX = Pattern.compile("ComponentInfo\\{(.*)\\}");
private final UiDevice mDevice;
private final LauncherInstrumentation mLauncher;
@@ -101,6 +104,9 @@
@Rule
public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule();
+ @Rule
+ public ExtendedLongPressTimeoutRule mLongPressTimeoutRule = new ExtendedLongPressTimeoutRule();
+
public FallbackRecentsTest() throws RemoteException {
Instrumentation instrumentation = getInstrumentation();
Context context = instrumentation.getContext();
@@ -108,8 +114,6 @@
mDevice.setOrientationNatural();
mLauncher = AbstractLauncherUiTest.createLauncherInstrumentation();
mLauncher.enableDebugTracing();
- // b/143488140
- //mLauncher.enableCheckEventsForSuccessfulGestures();
if (TestHelpers.isInLauncherProcess()) {
Utilities.enableRunningInTestHarnessForTests();
@@ -132,7 +136,6 @@
TestCommandReceiver.callCommand(TestCommandReceiver.DISABLE_TEST_LAUNCHER);
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
getLauncherCommand(getLauncherInMyProcess()));
- // b/143488140
pressHomeAndWaitForOverviewClose();
}
}
@@ -176,8 +179,6 @@
}
}
- // b/143488140
- //@NavigationModeSwitch
@Test
public void goToOverviewFromHome() {
mDevice.pressHome();
@@ -187,9 +188,6 @@
mLauncher.getLaunchedAppState().switchToOverview();
}
- // Staging; will be promoted to presubmit if stable
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
-
//@NavigationModeSwitch
@Test
public void goToOverviewFromApp() {
@@ -249,11 +247,8 @@
DEFAULT_UI_TIMEOUT, mLauncher);
}
- // b/143488140
- //@NavigationModeSwitch
@Test
- @ScreenRecordRule.ScreenRecord // b/321775748
- public void testOverview() {
+ public void testOverview() throws IOException {
startAppFast(getAppPackageName());
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
startTestActivity(2);
@@ -261,7 +256,10 @@
Wait.atMost("Expected three apps in the task list",
() -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
+ checkTestLauncher();
BaseOverview overview = mLauncher.getLaunchedAppState().switchToOverview();
+ checkTestLauncher();
+
executeOnRecents(recents -> {
assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3);
});
@@ -303,6 +301,17 @@
mOtherLauncherActivity.packageName).text(FALLBACK_LAUNCHER_TITLE)), WAIT_TIME_MS));
}
+ private void checkTestLauncher() throws IOException {
+ final Matcher matcher = COMPONENT_INFO_REGEX.matcher(
+ mDevice.executeShellCommand("cmd shortcut get-default-launcher"));
+ assertTrue("Incorrect output from get-default-launcher", matcher.find());
+ assertEquals("Current Launcher activity is incorrect",
+ "com.google.android.apps.nexuslauncher.tests/com.android"
+ + ".launcher3.testcomponent.TestLauncherActivity",
+ matcher.group(1)
+ );
+ }
+
private int getCurrentOverviewPage(RecentsActivity recents) {
return recents.<RecentsView>getOverviewPanel().getCurrentPage();
}
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index df88726..4459ed6 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -162,8 +162,8 @@
final Context targetContext = getInstrumentation().getTargetContext();
final DisplayController.DisplayInfoChangeListener listener =
(context, info, flags) -> {
- if (LauncherInstrumentation.getNavigationModel(info.navigationMode.resValue)
- == expectedMode) {
+ if (LauncherInstrumentation.getNavigationModel(
+ info.getNavigationMode().resValue) == expectedMode) {
latch.countDown();
}
};
@@ -205,11 +205,10 @@
boolean condition, Description description) {
launcher.checkForAnomaly(true, true);
if (!condition) {
- final AssertionError assertionError = new AssertionError(message);
if (description != null) {
- FailureWatcher.onError(launcher, description, assertionError);
+ FailureWatcher.onError(launcher, description);
}
- throw assertionError;
+ throw new AssertionError(message);
}
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index ed5526f..03244eb 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -48,6 +48,8 @@
@Mock
private SystemUiProxy mockSystemUiProxy;
+ @Mock
+ private TopTaskTracker mTopTaskTracker;
// Class under test
private RecentTasksList mRecentTasksList;
@@ -58,7 +60,7 @@
LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
KeyguardManager mockKeyguardManager = mock(KeyguardManager.class);
mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManager,
- mockSystemUiProxy);
+ mockSystemUiProxy, mTopTaskTracker);
}
@Test
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index 2916952..80fbce7 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -9,8 +9,19 @@
import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
import com.android.launcher3.util.DisplayController.Info
import com.android.launcher3.util.NavigationMode
-import com.android.launcher3.util.window.WindowManagerProxy
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
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -18,6 +29,7 @@
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
@@ -27,7 +39,6 @@
class RecentsAnimationDeviceStateTest {
@Mock private lateinit var exclusionManager: GestureExclusionManager
- @Mock private lateinit var windowManagerProxy: WindowManagerProxy
@Mock private lateinit var info: Info
private val context = ApplicationProvider.getApplicationContext() as Context
@@ -79,7 +90,7 @@
@Test
fun onDisplayInfoChanged_noButton_registerExclusionListener() {
- whenever(windowManagerProxy.getNavigationMode(context)).thenReturn(NavigationMode.NO_BUTTON)
+ doReturn(NavigationMode.NO_BUTTON).whenever(info).getNavigationMode()
underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE)
@@ -107,4 +118,88 @@
verifyZeroInteractions(exclusionManager)
}
+
+ @Test
+ fun trackpadGesturesNotAllowedForSelectedStates() {
+ val disablingStates = GESTURE_DISABLING_SYSUI_STATES +
+ SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
+
+ allSysUiStates().forEach { state ->
+ val canStartGesture = !disablingStates.contains(state)
+ underTest.setSystemUiFlags(state)
+ assertThat(underTest.canStartTrackpadGesture()).isEqualTo(canStartGesture)
+ }
+ }
+
+ @Test
+ fun trackpadGesturesNotAllowedIfHomeAndOverviewIsDisabled() {
+ val stateToExpectedResult = mapOf(
+ SYSUI_STATE_HOME_DISABLED to true,
+ SYSUI_STATE_OVERVIEW_DISABLED to true,
+ DEFAULT_STATE
+ .enable(SYSUI_STATE_OVERVIEW_DISABLED)
+ .enable(SYSUI_STATE_HOME_DISABLED) to false
+ )
+
+ stateToExpectedResult.forEach { (state, allowed) ->
+ underTest.setSystemUiFlags(state)
+ assertThat(underTest.canStartTrackpadGesture()).isEqualTo(allowed)
+ }
+ }
+
+ @Test
+ fun systemGesturesNotAllowedForSelectedStates() {
+ val disablingStates = GESTURE_DISABLING_SYSUI_STATES + SYSUI_STATE_NAV_BAR_HIDDEN
+
+ allSysUiStates().forEach { state ->
+ val canStartGesture = !disablingStates.contains(state)
+ underTest.setSystemUiFlags(state)
+ assertThat(underTest.canStartSystemGesture()).isEqualTo(canStartGesture)
+ }
+ }
+
+ @Test
+ fun systemGesturesNotAllowedWhenGestureStateDisabledAndNavBarVisible() {
+ val stateToExpectedResult = mapOf(
+ DEFAULT_STATE
+ .enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+ .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+ DEFAULT_STATE
+ .enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+ .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+ DEFAULT_STATE
+ .disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+ .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+ DEFAULT_STATE
+ .disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+ .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to false,
+ )
+
+ stateToExpectedResult.forEach {(state, gestureAllowed) ->
+ underTest.setSystemUiFlags(state)
+ assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed)
+ }
+ }
+
+ private fun allSysUiStates(): List<Long> {
+ // SYSUI_STATES_* are binary flags
+ return (0..SYSUI_STATES_COUNT).map { 1L shl it }
+ }
+
+ companion object {
+ private val GESTURE_DISABLING_SYSUI_STATES = listOf(
+ SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
+ SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
+ SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
+ SYSUI_STATE_MAGNIFICATION_OVERLAP,
+ SYSUI_STATE_DEVICE_DREAMING,
+ SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
+ )
+ private const val SYSUI_STATES_COUNT = 33
+ private const val DEFAULT_STATE = 0L
+ }
+
+ private fun Long.enable(state: Long) = this or state
+
+ private fun Long.disable(state: Long) = this and state.inv()
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
index 4aa7cb0..07d8f61 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
@@ -86,9 +86,10 @@
final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
- assertTrue("Latest task is not Calculator",
- CALCULATOR_PACKAGE.equals(task.getTask().getTopComponent().getPackageName()));
- return task.getDigitalWellBeingToast();
+ TaskView.TaskContainer taskContainer = task.getTaskContainers().get(0);
+ assertTrue("Latest task is not Calculator", CALCULATOR_PACKAGE.equals(
+ taskContainer.getTask().getTopComponent().getPackageName()));
+ return taskContainer.getDigitalWellBeingToast();
});
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
index 3f3b9ed..b7fd8be 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
@@ -27,6 +27,7 @@
import com.android.launcher3.tapl.OverviewTask.OverviewSplitTask;
import com.android.launcher3.tapl.OverviewTaskMenu;
import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Test;
@@ -35,7 +36,7 @@
* This test run in both Out of process (Oop) and in-process (Ipc).
* Tests the app Icon in overview.
*/
-public class TaplOverviewIconTest extends AbstractLauncherUiTest {
+public class TaplOverviewIconTest extends AbstractLauncherUiTest<QuickstepLauncher> {
private static final String CALCULATOR_APP_PACKAGE =
resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
@@ -87,6 +88,8 @@
}
private void createAndLaunchASplitPair() {
+ clearAllRecentTasks();
+
startTestActivity(2);
startTestActivity(3);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
new file mode 100644
index 0000000..23a29f7
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import com.android.launcher3.allapps.PrivateProfileManager;
+import com.android.launcher3.tapl.HomeAllApps;
+import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.PrivateSpaceContainer;
+import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.rule.ScreenRecordRule;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Objects;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplPrivateSpaceTest extends AbstractQuickStepTest {
+
+ private int mProfileUserId;
+
+ private static final String PRIVATE_PROFILE_NAME = "LauncherPrivateProfile";
+ private static final String INSTALLED_APP_NAME = "Aardwolf";
+ private static final int MAX_STATE_TOGGLE_TRIES = 2;
+ private static final String TAG = "TaplPrivateSpaceTest";
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ initialize(this);
+
+ createAndStartPrivateProfileUser();
+
+ mDevice.pressHome();
+ waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+ waitForStateTransitionToEnd("Launcher internal state didn't switch to Normal",
+ () -> NORMAL);
+ waitForResumed("Launcher internal state is still Background");
+ mLauncher.getWorkspace().switchToAllApps();
+ waitForStateTransitionToEnd("Launcher internal state didn't switch to All Apps",
+ () -> ALL_APPS);
+
+ // Wait for Private Space being available in Launcher.
+ waitForPrivateSpaceSetup();
+ // Wait for Launcher UI to be updated with Private Space Items.
+ waitForLauncherUIUpdate();
+ }
+
+ private void createAndStartPrivateProfileUser() {
+ String createUserOutput = executeShellCommand("pm create-user --profileOf 0 --user-type "
+ + "android.os.usertype.profile.PRIVATE " + PRIVATE_PROFILE_NAME);
+ updatePrivateProfileSetupSuccessful("pm create-user", createUserOutput);
+ String[] tokens = createUserOutput.split("\\s+");
+ mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
+ StringBuilder logStr = new StringBuilder().append("profileId: ").append(mProfileUserId);
+ for (String str : tokens) {
+ logStr.append(str).append("\n");
+ }
+ String startUserOutput = executeShellCommand("am start-user " + mProfileUserId);
+ updatePrivateProfileSetupSuccessful("am start-user", startUserOutput);
+ }
+
+ @After
+ public void removePrivateProfile() {
+ String userListOutput = executeShellCommand("pm list users");
+ if (isPrivateProfilePresent("pm list users", userListOutput)) {
+ String output = executeShellCommand("pm remove-user " + mProfileUserId);
+ updateProfileRemovalSuccessful("pm remove-user", output);
+ waitForPrivateSpaceRemoval();
+ }
+ }
+
+ @Test
+ public void testPrivateSpaceContainerIsPresent() {
+ // Scroll to the bottom of All Apps
+ executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+ // Freeze All Apps
+ HomeAllApps homeAllApps = mLauncher.getAllApps();
+ homeAllApps.freeze();
+
+ try {
+ // Verify Unlocked View elements are present.
+ assertNotNull("Private Space Unlocked View not found, or is not correct",
+ homeAllApps.getPrivateSpaceUnlockedView());
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
+ }
+
+ @Test
+ @ScreenRecordRule.ScreenRecord // b/334946529
+ public void testUserInstalledAppIsShownAboveDivider() throws IOException {
+ // Ensure that the App is not installed in main user otherwise, it may not be found in
+ // PS container.
+ TestUtil.uninstallDummyApp();
+ // Install the app in Private Profile
+ TestUtil.installDummyAppForUser(mProfileUserId);
+ waitForLauncherUIUpdate();
+ // Scroll to the bottom of All Apps
+ executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+ // Freeze All Apps
+ HomeAllApps homeAllApps = mLauncher.getAllApps();
+ homeAllApps.freeze();
+
+ try {
+ // Verify the Installed App is displayed in correct position.
+ PrivateSpaceContainer psContainer = homeAllApps.getPrivateSpaceUnlockedView();
+ psContainer.verifyInstalledAppIsPresent(INSTALLED_APP_NAME);
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
+ }
+
+ @Test
+ @ScreenRecordRule.ScreenRecord // b/334946529
+ public void testPrivateSpaceAppLongPressUninstallMenu() throws IOException {
+ // Ensure that the App is not installed in main user otherwise, it may not be found in
+ // PS container.
+ TestUtil.uninstallDummyApp();
+ // Install the app in Private Profile
+ TestUtil.installDummyAppForUser(mProfileUserId);
+ waitForLauncherUIUpdate();
+ // Scroll to the bottom of All Apps
+ executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+ // Freeze All Apps
+ HomeAllApps homeAllApps = mLauncher.getAllApps();
+ homeAllApps.freeze();
+
+ try {
+ // Get the "uninstall" menu item.
+ homeAllApps.getAppIcon(INSTALLED_APP_NAME).openMenu().getMenuItem("Uninstall app");
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
+ }
+
+ @Test
+ @ScreenRecordRule.ScreenRecord // b/334946529
+ public void testPrivateSpaceLockingBehaviour() throws IOException {
+ // Scroll to the bottom of All Apps
+ executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+ HomeAllApps homeAllApps = mLauncher.getAllApps();
+
+ // Disable Private Space
+ togglePrivateSpaceWithRetry(PrivateProfileManager.STATE_DISABLED, homeAllApps);
+ // Scroll to the bottom of All Apps
+ executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+
+ homeAllApps.freeze();
+ try {
+ // Verify Locked View elements are present.
+ homeAllApps.getPrivateSpaceLockedView();
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
+
+ // Enable Private Space
+ togglePrivateSpaceWithRetry(PrivateProfileManager.STATE_ENABLED, homeAllApps);
+ // Scroll to the bottom of All Apps
+ executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+
+ homeAllApps.freeze();
+ try {
+ // Verify UnLocked View elements are present.
+ homeAllApps.getPrivateSpaceUnlockedView();
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
+ }
+
+ private void togglePrivateSpace(int state, HomeAllApps homeAllApps) {
+ homeAllApps.freeze();
+ try {
+ // Try Toggling Private Space
+ homeAllApps.togglePrivateSpace();
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
+ PrivateProfileManager manager = getFromLauncher(l -> l.getAppsView()
+ .getPrivateProfileManager());
+ waitForLauncherCondition("Private profile toggle to state: " + state + " failed",
+ launcher -> {
+ manager.reset();
+ return manager.getCurrentState() == state;
+ },
+ LauncherInstrumentation.WAIT_TIME_MS);
+ // Wait for Launcher UI to be updated with Private Space Items.
+ waitForLauncherUIUpdate();
+ }
+
+ private void togglePrivateSpaceWithRetry(int state, HomeAllApps homeAllApps) {
+ int togglePsCount = 0;
+ boolean shouldRetry;
+ do {
+ togglePsCount ++;
+ try {
+ togglePrivateSpace(state, homeAllApps);
+ // No need to retry if the toggle was successful.
+ shouldRetry = false;
+ } catch (AssertionError error) {
+ if (togglePsCount < MAX_STATE_TOGGLE_TRIES) {
+ shouldRetry = true;
+ } else {
+ throw error;
+ }
+ }
+ } while (shouldRetry);
+ }
+
+ private void waitForPrivateSpaceSetup() {
+ waitForLauncherCondition("Private Profile not setup",
+ launcher -> launcher.getAppsView().hasPrivateProfile(),
+ LauncherInstrumentation.WAIT_TIME_MS);
+ }
+
+ private void waitForPrivateSpaceRemoval() {
+ waitForLauncherCondition("Private Profile not setup",
+ launcher -> !launcher.getAppsView().hasPrivateProfile(),
+ LauncherInstrumentation.WAIT_TIME_MS);
+ }
+
+ private void waitForLauncherUIUpdate() {
+ // Wait for model thread completion as it may be processing
+ // the install event from the SystemService
+ mLauncher.waitForModelQueueCleared();
+ // Wait for Launcher UI thread completion, as it may be processing updating the UI in
+ // response to the model update. Not that `waitForLauncherInitialized` is just a proxy
+ // method, we can use any method which touches Launcher UI thread,
+ mLauncher.waitForLauncherInitialized();
+ }
+
+ private void updatePrivateProfileSetupSuccessful(String cli, String output) {
+ Log.d(TAG, "updatePrivateProfileSetupSuccessful, cli=" + cli + " " + "output="
+ + output);
+ assertTrue(output, output.startsWith("Success"));
+ }
+
+ private void updateProfileRemovalSuccessful(String cli, String output) {
+ Log.d(TAG, "updateProfileRemovalSuccessful, cli=" + cli + " " + "output=" + output);
+ assertTrue(output, output.startsWith("Success"));
+ }
+
+ private boolean isPrivateProfilePresent(String cli, String output) {
+ Log.d(TAG, "updatePrivateProfilePresent, cli=" + cli + " " + "output=" + output);
+ return output.contains(PRIVATE_PROFILE_NAME);
+ }
+
+ private String executeShellCommand(String command) {
+ try {
+ return mDevice.executeShellCommand(command);
+ } catch (IOException e) {
+ Log.e(TAG, "error running shell command", e);
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
index b31f470..1886ce6 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
@@ -16,13 +16,9 @@
package com.android.quickstep;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
-
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import org.junit.Before;
@@ -39,7 +35,6 @@
@Before
public void setUp() throws Exception {
super.setUp();
- // b/143488140
mLauncher.goHome();
// Start an activity where the gestures start.
startTestActivity(2);
@@ -47,8 +42,6 @@
@Test
@NavigationModeSwitch
- // Stress tests are long. We permanently demote them from presubmit to match the presubmit SLO.
- @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
public void testStressPressHome() {
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
// Destroy Launcher activity.
@@ -61,8 +54,6 @@
@Test
@NavigationModeSwitch
- // Stress tests are long. We permanently demote them from presubmit to match the presubmit SLO.
- @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
public void testStressSwipeToOverview() {
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
// Destroy Launcher activity.
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
index 7c2d41d..43ebb17 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
@@ -149,6 +149,13 @@
runTest(TestSurface.WIDGETS, TestCase.LAUNCH_OVERVIEW);
}
+ @Test
+ public void testLaunchSingleRecentTask() {
+ mLauncher.getLaunchedAppState().switchToOverview().dismissAllTasks();
+ startAppFast(CALCULATOR_APP_PACKAGE);
+ mLauncher.goHome().showQuickSwitchView().launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
+ }
+
private void runTest(@NonNull TestSurface testSurface, @NonNull TestCase testCase) {
for (int i = 0; i < testCase.mNumAdditionalRunningTasks; i++) {
startTestActivity(3 + i);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 45a9527..7877e8a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -21,6 +21,7 @@
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
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 static org.junit.Assume.assumeFalse;
@@ -35,9 +36,9 @@
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
-import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.tapl.BaseOverview;
import com.android.launcher3.tapl.LaunchedAppState;
import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
import com.android.launcher3.tapl.Overview;
@@ -55,6 +56,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -179,12 +181,6 @@
public void testOverviewActions() throws Exception {
assumeFalse("Skipping Overview Actions tests for grid only overview",
mLauncher.isTablet() && mLauncher.isGridOnlyOverviewEnabled());
- // Experimenting for b/165029151:
- final Overview overview = mLauncher.goHome().switchToOverview();
- if (overview.hasTasks()) overview.dismissAllTasks();
- mLauncher.goHome();
- //
-
startTestAppsWithCheck();
OverviewActions actionsView =
mLauncher.goHome().switchToOverview().getOverviewActions();
@@ -421,7 +417,6 @@
READ_DEVICE_CONFIG_PERMISSION);
// Debug if we need to goHome to prevent wrong previous state b/315525621
mLauncher.goHome();
- assumeFalse(Flags.enablePredictiveBackGesture());
mLauncher.getWorkspace().switchToAllApps().pressBackToWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
@@ -433,8 +428,7 @@
@Test
@PortraitLandscape
@TaskbarModeSwitch()
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/309820115
- @ScreenRecord // b/309820115
+ @Ignore("b/315376057")
public void testOverviewForTablet() throws Exception {
assumeTrue(mLauncher.isTablet());
@@ -503,7 +497,6 @@
@Test
@PortraitLandscape
- @ScreenRecord // b/326839375
public void testOverviewDeadzones() throws Exception {
startTestAppsWithCheck();
@@ -583,4 +576,25 @@
mLauncher.getDevice().setOrientationNatural();
}
}
+
+ @Test
+ public void testExcludeFromRecents() throws Exception {
+ startExcludeFromRecentsTestActivity();
+ OverviewTask currentTask = getAndAssertLaunchedApp().switchToOverview().getCurrentTask();
+ // TODO(b/326565120): the expected content description shouldn't be null but for now there
+ // is a bug that causes it to sometimes be for excludeForRecents tasks.
+ assertTrue("Can't find ExcludeFromRecentsTestActivity after entering Overview from it",
+ currentTask.containsContentDescription("ExcludeFromRecents")
+ || currentTask.containsContentDescription(null));
+ // Going home should clear out the excludeFromRecents task.
+ BaseOverview overview = mLauncher.goHome().switchToOverview();
+ if (overview.hasTasks()) {
+ currentTask = overview.getCurrentTask();
+ assertFalse("Found ExcludeFromRecentsTestActivity after entering Overview from Home",
+ currentTask.containsContentDescription("ExcludeFromRecents")
+ || currentTask.containsContentDescription(null));
+ } else {
+ // Presumably the test started with 0 tasks and remains that way after going home.
+ }
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index ec8e00d..8adf793 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -33,9 +33,7 @@
import com.android.launcher3.tapl.Overview;
import com.android.launcher3.tapl.Taskbar;
import com.android.launcher3.tapl.TaskbarAppIcon;
-import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.rule.TestStabilityRule;
-import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
import com.android.wm.shell.Flags;
import org.junit.After;
@@ -74,14 +72,6 @@
}
@Test
- @PortraitLandscape
- public void testSplitFromOverview() {
- createAndLaunchASplitPair();
- }
-
- @Test
- @PortraitLandscape
- @TaskbarModeSwitch
@TestStabilityRule.Stability(flavors = PLATFORM_POSTSUBMIT | LOCAL) // b/295225524
public void testSplitAppFromHomeWithItself() throws Exception {
// Currently only tablets have Taskbar in Overview, so test is only active on tablets
@@ -117,18 +107,19 @@
}
@Test
- public void testSaveAppPairMenuItemExistsOnSplitPair() throws Exception {
+ public void testSaveAppPairMenuItemOrActionExistsOnSplitPair() {
assumeTrue("App pairs feature is currently not enabled, no test needed",
Flags.enableAppPairs());
createAndLaunchASplitPair();
- assertTrue("Save app pair menu item is missing",
- mLauncher.goHome()
- .switchToOverview()
- .getCurrentTask()
- .tapMenu()
- .hasMenuItem("Save app pair"));
+ Overview overview = mLauncher.goHome().switchToOverview();
+ if (mLauncher.isGridOnlyOverviewEnabled() || !mLauncher.isTablet()) {
+ assertTrue("Save app pair menu item is missing",
+ overview.getCurrentTask()
+ .tapMenu()
+ .hasMenuItem("Save app pair"));
+ }
}
@Test
@@ -151,11 +142,7 @@
// Currently only tablets have Taskbar in Overview, so test is only active on tablets
assumeTrue(mLauncher.isTablet());
- if (!mLauncher.getRecentTasks().isEmpty()) {
- // Clear all recent tasks
- mLauncher.goHome().switchToOverview().dismissAllTasks();
- }
-
+ clearAllRecentTasks();
startAppFast(getAppPackageName());
Overview overview = mLauncher.goHome().switchToOverview();
@@ -172,6 +159,8 @@
}
private void createAndLaunchASplitPair() {
+ clearAllRecentTasks();
+
startTestActivity(2);
startTestActivity(3);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
index f0683f9..ec245ee 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
import static com.android.quickstep.TaplTestsTaskbar.TaskbarMode.PERSISTENT;
import static com.android.quickstep.TaplTestsTaskbar.TaskbarMode.TRANSIENT;
@@ -53,7 +55,7 @@
@Override
public void setUp() throws Exception {
- mTaskbarWasInTransientMode = isTaskbarInTransientMode(mTargetContext);
+ mTaskbarWasInTransientMode = isTaskbarInTransientMode(getTargetContext());
setTaskbarMode(mLauncher, isTaskbarTestModeTransient());
super.setUp();
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
index aa8c7b5..2c23f86 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
@@ -16,10 +16,11 @@
package com.android.quickstep;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.app.Instrumentation;
@@ -28,13 +29,15 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.Flags;
import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.util.rule.ScreenRecordRule;
+import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,8 +48,14 @@
private static final String READ_DEVICE_CONFIG_PERMISSION =
"android.permission.READ_DEVICE_CONFIG";
+ @Before
+ public void setup() {
+ mLauncher.injectFakeTrackpad();
+ }
+
@After
public void tearDown() {
+ mLauncher.ejectFakeTrackpad();
mLauncher.setTrackpadGestureType(TrackpadGestureType.NONE);
}
@@ -69,7 +78,6 @@
@NavigationModeSwitch(mode = ZERO_BUTTON)
public void pressBack() throws Exception {
assumeTrue(mLauncher.isTablet());
- assumeFalse(Flags.enablePredictiveBackGesture());
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
try {
@@ -87,6 +95,8 @@
@Test
@PortraitLandscape
@NavigationModeSwitch
+ @ScreenRecordRule.ScreenRecord // b/336606166
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/336606166
public void switchToOverview() throws Exception {
assumeTrue(mLauncher.isTablet());
@@ -107,8 +117,8 @@
}
@Test
- @NavigationModeSwitch
@PortraitLandscape
+ @NavigationModeSwitch
public void testQuickSwitchFromHome() throws Exception {
assumeTrue(mLauncher.isTablet());
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
index 38d6046..4b20d60 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
@@ -24,8 +24,6 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
import org.junit.Test;
@@ -69,8 +67,6 @@
@Test
@TaskbarModeSwitch(mode = TRANSIENT)
- @PortraitLandscape
- @ScreenRecord // b/317798731
public void testSwipeToStashAndUnstash() {
getTaskbar().swipeDownToStash();
mLauncher.getLaunchedAppState().swipeUpToUnstashTaskbar();
diff --git a/quickstep/tests/src/com/android/quickstep/TaplViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/TaplViewInflationDuringSwipeUp.java
deleted file mode 100644
index 6093816..0000000
--- a/quickstep/tests/src/com/android/quickstep/TaplViewInflationDuringSwipeUp.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
-import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
-import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
-import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
-
-import android.appwidget.AppWidgetManager;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.RemoteViews;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.Suppress;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.Until;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.celllayout.FavoriteItemsTransaction;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.tapl.LaunchedAppState;
-import com.android.launcher3.testcomponent.ListViewService;
-import com.android.launcher3.testcomponent.ListViewService.SimpleViewsFactory;
-import com.android.launcher3.testcomponent.TestCommandReceiver;
-import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.Executors;
-import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.lang.reflect.Field;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.IntConsumer;
-
-/**
- * Test to verify view inflation does not happen during swipe up.
- * To verify view inflation, we setup a stub ViewConfiguration and check if any call to that class
- * does from a View.init method or not.
- *
- * Alternative approaches considered:
- * Overriding LayoutInflater: This does not cover views initialized
- * directly (ex: new LinearLayout)
- * Using ExtendedMockito: Mocking static methods from platform classes (loaded in zygote) makes
- * the main thread extremely slow and untestable
- */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class TaplViewInflationDuringSwipeUp extends AbstractQuickStepTest {
-
- private SparseArray<ViewConfiguration> mConfigMap;
- private InitTracker mInitTracker;
- private LauncherModel mModel;
-
- @Before
- public void setUp() throws Exception {
- // Workaround for b/142351228, when there are no activities, the system may not destroy the
- // activity correctly for activities under instrumentation, which can leave two concurrent
- // activities, which changes the order in which the activities are cleaned up (overlapping
- // stop and start) leading to all sort of issues. To workaround this, ensure that the test
- // is started only after starting another app.
- startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
-
- super.setUp();
-
- mModel = LauncherAppState.getInstance(mTargetContext).getModel();
- Executors.MODEL_EXECUTOR.submit(mModel.getModelDbController()::createEmptyDB).get();
-
- // Get static configuration map
- Field field = ViewConfiguration.class.getDeclaredField("sConfigurations");
- field.setAccessible(true);
- mConfigMap = (SparseArray<ViewConfiguration>) field.get(null);
-
- mInitTracker = new InitTracker();
- }
-
- @Test
- @NavigationModeSwitch(mode = ZERO_BUTTON)
- @Suppress // until b/190618549 is fixed
- public void testSwipeUpFromApp() throws Exception {
- try {
- // Go to overview once so that all views are initialized and cached
- startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
- mLauncher.getLaunchedAppState().switchToOverview().dismissAllTasks();
-
- // Track view creations
- mInitTracker.startTracking();
-
- startTestActivity(2);
- mLauncher.getLaunchedAppState().switchToOverview();
-
- assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
- } finally {
- mConfigMap.clear();
- }
- }
-
- @Test
- @NavigationModeSwitch(mode = ZERO_BUTTON)
- @Suppress // until b/190729479 is fixed
- public void testSwipeUpFromApp_widget_update() {
- String stubText = "Some random stub text";
-
- executeSwipeUpTestWithWidget(
- widgetId -> { },
- widgetId -> AppWidgetManager.getInstance(getContext())
- .updateAppWidget(widgetId, createMainWidgetViews(stubText)),
- stubText);
- }
-
- @Test
- @NavigationModeSwitch(mode = ZERO_BUTTON)
- @Suppress // until b/190729479 is fixed
- public void testSwipeUp_with_list_widgets() {
- SimpleViewsFactory viewFactory = new SimpleViewsFactory();
- viewFactory.viewCount = 1;
- Bundle args = new Bundle();
- args.putBinder(EXTRA_VALUE, viewFactory.toBinder());
- TestCommandReceiver.callCommand(SET_LIST_VIEW_SERVICE_BINDER, null, args);
-
- try {
- executeSwipeUpTestWithWidget(
- widgetId -> {
- // Initialize widget
- RemoteViews views = createMainWidgetViews("List widget title");
- views.setRemoteAdapter(android.R.id.list,
- new Intent(getContext(), ListViewService.class));
- AppWidgetManager.getInstance(getContext()).updateAppWidget(widgetId, views);
- verifyWidget(viewFactory.getLabel(0));
- },
- widgetId -> {
- // Update widget
- viewFactory.viewCount = 2;
- AppWidgetManager.getInstance(getContext())
- .notifyAppWidgetViewDataChanged(widgetId, android.R.id.list);
- },
- viewFactory.getLabel(1)
- );
- } finally {
- TestCommandReceiver.callCommand(SET_LIST_VIEW_SERVICE_BINDER, null, new Bundle());
- }
- }
-
- private void executeSwipeUpTestWithWidget(IntConsumer widgetIdCreationCallback,
- IntConsumer updateBeforeSwipeUp, String finalWidgetText) {
- try {
- LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(false);
-
- // Make sure the widget is big enough to show a list of items
- info.minSpanX = 2;
- info.minSpanY = 2;
- info.spanX = 2;
- info.spanY = 2;
- AtomicInteger widgetId = new AtomicInteger();
- new FavoriteItemsTransaction(mTargetContext)
- .addItem(() -> {
- LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, true);
- item.screenId = FIRST_SCREEN_ID;
- widgetId.set(item.appWidgetId);
- return item;
- })
- .commitAndLoadHome(mLauncher);
-
-
-
- assertTrue("Widget is not present",
- mLauncher.goHome().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
-
- // Verify widget id
- widgetIdCreationCallback.accept(widgetId.get());
-
- // Go to overview once so that all views are initialized and cached
- startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
- mLauncher.getLaunchedAppState().switchToOverview().dismissAllTasks();
-
- // Track view creations
- mInitTracker.startTracking();
-
- startTestActivity(2);
- LaunchedAppState launchedAppState = mLauncher.getLaunchedAppState();
-
- // Update widget
- updateBeforeSwipeUp.accept(widgetId.get());
-
- launchedAppState.switchToOverview();
- assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
-
- // Widget is updated when going home
- mInitTracker.disableLog();
- mLauncher.goHome();
- verifyWidget(finalWidgetText);
- assertNotEquals(1, mInitTracker.viewInitCount);
- } finally {
- mConfigMap.clear();
- }
- }
-
- private void verifyWidget(String text) {
- assertNotNull("Widget not updated",
- UiDevice.getInstance(getInstrumentation())
- .wait(Until.findObject(By.text(text)), DEFAULT_UI_TIMEOUT));
- }
-
- private RemoteViews createMainWidgetViews(String title) {
- Context c = getContext();
- int layoutId = c.getResources().getIdentifier(
- "test_layout_widget_list", "layout", c.getPackageName());
- RemoteViews views = new RemoteViews(c.getPackageName(), layoutId);
- views.setTextViewText(android.R.id.text1, title);
- return views;
- }
-
- private class InitTracker implements Answer {
-
- public int viewInitCount = 0;
-
- public boolean log = true;
-
- @Override
- public Object answer(InvocationOnMock invocation) throws Throwable {
- Exception ex = new Exception();
-
- boolean found = false;
- for (StackTraceElement ste : ex.getStackTrace()) {
- if ("<init>".equals(ste.getMethodName())
- && View.class.getName().equals(ste.getClassName())) {
- found = true;
- break;
- }
- }
- if (found) {
- viewInitCount++;
- if (log) {
- Log.d("InitTracker", "New view inflated", ex);
- }
-
- }
- return invocation.callRealMethod();
- }
-
- public void disableLog() {
- log = false;
- }
-
- public void startTracking() {
- ViewConfiguration vc = ViewConfiguration.get(mTargetContext);
- ViewConfiguration spyVC = spy(vc);
- mConfigMap.put(mConfigMap.keyAt(mConfigMap.indexOfValue(vc)), spyVC);
- doAnswer(this).when(spyVC).getScaledTouchSlop();
- }
- }
-}
diff --git a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
index d744194..512557b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
@@ -17,9 +17,11 @@
package com.android.quickstep;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -34,7 +36,7 @@
import androidx.test.filters.SmallTest;
-import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.util.BorderAnimator;
import com.android.quickstep.views.TaskView;
@@ -47,7 +49,7 @@
public class TaskViewTest {
@Mock
- private StatefulActivity mContext;
+ private QuickstepLauncher mContext;
@Mock
private Resources mResource;
@Mock
@@ -73,6 +75,7 @@
@Test
public void notShowBorderOnBorderDisabled() {
+ presetBorderStatus(/* enabled= */ true);
mTaskView.setBorderEnabled(/* enabled= */ false);
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
mTaskView.onHoverEvent(MotionEvent.obtain(event));
@@ -85,7 +88,7 @@
}
@Test
- public void showBorderOnBorderEnabled() {
+ public void showBorderOnHoverEvent() {
mTaskView.setBorderEnabled(/* enabled= */ true);
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
mTaskView.onHoverEvent(MotionEvent.obtain(event));
@@ -97,7 +100,18 @@
}
@Test
+ public void showBorderOnBorderEnabled() {
+ presetBorderStatus(/* enabled= */ false);
+ mTaskView.setBorderEnabled(/* enabled= */ true);
+ verify(mHoverAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */
+ true);
+ verify(mFocusAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */
+ true);
+ }
+
+ @Test
public void hideBorderOnBorderDisabled() {
+ presetBorderStatus(/* enabled= */ true);
mTaskView.setBorderEnabled(/* enabled= */ false);
verify(mHoverAnimator, times(1)).setBorderVisibility(/* visible= */ false, /* animated= */
true);
@@ -106,13 +120,35 @@
}
@Test
+ public void notTriggerAnimatorWhenEnableStatusUnchanged() {
+ presetBorderStatus(/* enabled= */ false);
+ // Border is disabled by default, no animator is triggered after it is disabled again
+ mTaskView.setBorderEnabled(/* enabled= */ false);
+ verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */
+ anyBoolean(), /* animated= */ anyBoolean());
+ verify(mFocusAnimator, never()).setBorderVisibility(/* visible= */
+ anyBoolean(), /* animated= */ anyBoolean());
+ }
+
+ private void presetBorderStatus(boolean enabled) {
+ // Make the task view focused and hovered
+ MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
+ mTaskView.onHoverEvent(MotionEvent.obtain(event));
+ mTaskView.requestFocus();
+ mTaskView.setBorderEnabled(/* enabled= */ enabled);
+ // Reset invocation count after presetting status
+ reset(mHoverAnimator);
+ reset(mFocusAnimator);
+ }
+
+ @Test
public void notShowBorderByDefault() {
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
mTaskView.onHoverEvent(MotionEvent.obtain(event));
- verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */ false, /* animated= */
- true);
+ verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */
+ anyBoolean(), /* animated= */ anyBoolean());
mTaskView.onFocusChanged(true, 0, new Rect());
- verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */ false, /* animated= */
- true);
+ verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */
+ anyBoolean(), /* animated= */ anyBoolean());
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java
index e5657fb..84ceb33 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java
@@ -130,11 +130,10 @@
boolean condition, Description description) {
launcher.checkForAnomaly(true, true);
if (!condition) {
- final AssertionError assertionError = new AssertionError(message);
if (description != null) {
- FailureWatcher.onError(launcher, description, assertionError);
+ FailureWatcher.onError(launcher, description);
}
- throw assertionError;
+ throw new AssertionError(message);
}
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt b/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
index dbe4624..4d10f0f 100644
--- a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
+import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN
@@ -53,7 +54,13 @@
class TaskbarPinningControllerTest : TaskbarBaseTestCase() {
private val taskbarDragLayer = mock<TaskbarDragLayer>()
private val taskbarSharedState = mock<TaskbarSharedState>()
- private val launcherPrefs = mock<LauncherPrefs> { on { get(TASKBAR_PINNING) } doReturn false }
+ private var isInDesktopMode = false
+ private val isInDesktopModeProvider = { isInDesktopMode }
+ private val launcherPrefs =
+ mock<LauncherPrefs> {
+ on { get(TASKBAR_PINNING) } doReturn false
+ on { get(TASKBAR_PINNING_IN_DESKTOP_MODE) } doReturn false
+ }
private val statsLogger = mock<StatsLogManager.StatsLogger>()
private val statsLogManager = mock<StatsLogManager> { on { logger() } doReturn statsLogger }
private lateinit var pinningController: TaskbarPinningController
@@ -64,7 +71,8 @@
whenever(taskbarActivityContext.launcherPrefs).thenReturn(launcherPrefs)
whenever(taskbarActivityContext.dragLayer).thenReturn(taskbarDragLayer)
whenever(taskbarActivityContext.statsLogManager).thenReturn(statsLogManager)
- pinningController = spy(TaskbarPinningController(taskbarActivityContext))
+ pinningController =
+ spy(TaskbarPinningController(taskbarActivityContext, isInDesktopModeProvider))
pinningController.init(taskbarControllers, taskbarSharedState)
}
@@ -95,7 +103,7 @@
}
@Test
- fun testOnCloseCallback_whenPreferenceChanged_shouldAnimateToPinnedTaskbar() {
+ fun testOnCloseCallback_whenLauncherPreferenceChanged_shouldAnimateToPinnedTaskbar() {
whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
doNothing().whenever(pinningController).animateTaskbarPinning(any())
@@ -106,7 +114,7 @@
}
@Test
- fun testOnCloseCallback_whenPreferenceChanged_shouldAnimateToTransientTaskbar() {
+ fun testOnCloseCallback_whenLauncherPreferenceChanged_shouldAnimateToTransientTaskbar() {
whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(true)
doNothing().whenever(pinningController).animateTaskbarPinning(any())
@@ -199,4 +207,13 @@
assertThat(pinningController.isAnimatingTaskbarPinning).isFalse()
verify(launcherPrefs, times(1)).put(TASKBAR_PINNING, true)
}
+
+ @Test
+ fun testRecreateTaskbarAndUpdatePinningValue_whenAnimationEnds_shouldUpdateTaskbarPinningDesktopModePref() {
+ isInDesktopMode = true
+ pinningController.recreateTaskbarAndUpdatePinningValue()
+ verify(taskbarDragLayer, times(1)).setAnimatingTaskbarPinning(false)
+ assertThat(pinningController.isAnimatingTaskbarPinning).isFalse()
+ verify(launcherPrefs, times(1)).put(TASKBAR_PINNING_IN_DESKTOP_MODE, true)
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt b/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
new file mode 100644
index 0000000..b637e7d
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.taskbar.customization
+
+import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator
+import com.android.launcher3.taskbar.customization.TaskbarIconSpecs
+import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@RunWith(LauncherMultivalentJUnit::class)
+class TaskbarSpecsEvaluatorTest {
+
+ private val taskbarFeatureEvaluator = mock<TaskbarFeatureEvaluator>()
+ private val taskbarSpecsEvaluator = spy(TaskbarSpecsEvaluator(taskbarFeatureEvaluator))
+
+ @Test
+ fun testGetIconSizeByGrid_whenTaskbarIsTransient_withValidRowAndColumn() {
+ doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+ assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(6, 5))
+ .isEqualTo(TaskbarIconSpecs.iconSize52dp)
+ }
+
+ @Test
+ fun testGetIconSizeByGrid_whenTaskbarIsTransient_withInvalidRowAndColumn() {
+ doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+ assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(1, 2))
+ .isEqualTo(TaskbarIconSpecs.defaultTransientIconSize)
+ }
+
+ @Test
+ fun testGetIconSizeByGrid_whenTaskbarIsPersistent() {
+ doReturn(false).whenever(taskbarFeatureEvaluator).isTransient
+ assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(6, 5))
+ .isEqualTo(TaskbarIconSpecs.defaultPersistentIconSize)
+ }
+
+ @Test
+ fun testGetIconSizeStepDown_whenTaskbarIsPersistent() {
+ doReturn(false).whenever(taskbarFeatureEvaluator).isTransient
+ assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize44dp))
+ .isEqualTo(TaskbarIconSpecs.defaultPersistentIconSize)
+ }
+
+ @Test
+ fun testGetIconSizeStepDown_whenTaskbarIsTransientAndIconSizeAreInBound() {
+ doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+ assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize52dp))
+ .isEqualTo(TaskbarIconSpecs.iconSize48dp)
+ }
+
+ @Test
+ fun testGetIconSizeStepDown_whenTaskbarIsTransientAndIconSizeAreOutOfBound() {
+ doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+ assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize44dp))
+ .isEqualTo(TaskbarIconSpecs.iconSize44dp)
+ }
+
+ @Test
+ fun testGetIconSizeStepUp_whenTaskbarIsPersistent() {
+ doReturn(false).whenever(taskbarFeatureEvaluator).isTransient
+ assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize40dp))
+ .isEqualTo(TaskbarIconSpecs.iconSize40dp)
+ }
+
+ @Test
+ fun testGetIconSizeStepUp_whenTaskbarIsTransientAndIconSizeAreInBound() {
+ doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+ assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize44dp))
+ .isEqualTo(TaskbarIconSpecs.iconSize48dp)
+ }
+
+ @Test
+ fun testGetIconSizeStepUp_whenTaskbarIsTransientAndIconSizeAreOutOfBound() {
+ doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+ assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize52dp))
+ .isEqualTo(TaskbarIconSpecs.iconSize52dp)
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaplTestsPredictionRow.java b/quickstep/tests/src/com/android/quickstep/util/TaplTestsPredictionRow.java
new file mode 100644
index 0000000..4030b01
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/TaplTestsPredictionRow.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import com.android.quickstep.AbstractQuickStepTest;
+
+import org.junit.Test;
+
+public class TaplTestsPredictionRow extends AbstractQuickStepTest {
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mLauncher.getWorkspace().switchToAllApps();
+ }
+
+ @Test
+ public void testPredictionRow() {
+ mLauncher.getAllApps().getPredictionRowView();
+ }
+}
diff --git a/res/color-night-v31/material_color_surface_container_high.xml b/res/color-night-v31/material_color_surface_container_high.xml
index 002b88e..edd36fc 100644
--- a/res/color-night-v31/material_color_surface_container_high.xml
+++ b/res/color-night-v31/material_color_surface_container_high.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="12" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="17" />
</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_highest.xml b/res/color-night-v31/material_color_surface_container_highest.xml
index 002b88e..e54f953 100644
--- a/res/color-night-v31/material_color_surface_container_highest.xml
+++ b/res/color-night-v31/material_color_surface_container_highest.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="12" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="22" />
</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_low.xml b/res/color-night-v31/material_color_surface_container_low.xml
index 002b88e..40f0d4c 100644
--- a/res/color-night-v31/material_color_surface_container_low.xml
+++ b/res/color-night-v31/material_color_surface_container_low.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="12" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="10" />
</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_lowest.xml b/res/color-night-v31/material_color_surface_container_lowest.xml
index 002b88e..24f559b 100644
--- a/res/color-night-v31/material_color_surface_container_lowest.xml
+++ b/res/color-night-v31/material_color_surface_container_lowest.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="12" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="4" />
</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/taskbar_stroke.xml b/res/color-night-v31/taskbar_stroke.xml
new file mode 100644
index 0000000..db7a510
--- /dev/null
+++ b/res/color-night-v31/taskbar_stroke.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="#9AA0A6" />
+</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_high.xml b/res/color-v31/material_color_surface_container_high.xml
index b031c08..a996d51 100644
--- a/res/color-v31/material_color_surface_container_high.xml
+++ b/res/color-v31/material_color_surface_container_high.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="94" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="92" />
</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_highest.xml b/res/color-v31/material_color_surface_container_highest.xml
index b031c08..e7a535a 100644
--- a/res/color-v31/material_color_surface_container_highest.xml
+++ b/res/color-v31/material_color_surface_container_highest.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="94" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="90" />
</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_low.xml b/res/color-v31/material_color_surface_container_low.xml
index b031c08..b8fe01e 100644
--- a/res/color-v31/material_color_surface_container_low.xml
+++ b/res/color-v31/material_color_surface_container_low.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="94" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="96" />
</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_lowest.xml b/res/color-v31/material_color_surface_container_lowest.xml
index 674fc73..25e8666 100644
--- a/res/color-v31/material_color_surface_container_lowest.xml
+++ b/res/color-v31/material_color_surface_container_lowest.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="94" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="100" />
</selector>
\ No newline at end of file
diff --git a/res/color/taskbar_background_dark.xml b/res/color/taskbar_background_dark.xml
new file mode 100644
index 0000000..0727b8a
--- /dev/null
+++ b/res/color/taskbar_background_dark.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<!-- Should be the same as in packages/apps/Launcher3/res/color-night-v31/taskbar_background.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Want to use @android:color/system_neutral1_500, but that causes build errors -->
+ <item android:color="#76777D" android:lStar="6" />
+</selector>
\ No newline at end of file
diff --git a/res/color/taskbar_stroke.xml b/res/color/taskbar_stroke.xml
new file mode 100644
index 0000000..b691082
--- /dev/null
+++ b/res/color/taskbar_stroke.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="#BDC1C6" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_header.xml b/res/drawable/bg_ps_header.xml
index 526bb5a..da31445 100644
--- a/res/drawable/bg_ps_header.xml
+++ b/res/drawable/bg_ps_header.xml
@@ -14,9 +14,13 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners android:radius="@dimen/ps_container_corner_radius" />
- <solid android:color="?attr/materialColorSurfaceContainerHigh" />
-</shape>
\ No newline at end of file
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/accent_ripple_color">
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/ps_container_corner_radius" />
+ <solid android:color="?attr/materialColorSurfaceContainerHigh" />
+ </shape>
+ </item>
+</ripple>
diff --git a/res/drawable/bg_ps_mask_left_corner.xml b/res/drawable/bg_ps_mask_left_corner.xml
new file mode 100644
index 0000000..43eeedb
--- /dev/null
+++ b/res/drawable/bg_ps_mask_left_corner.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:viewportWidth="28"
+ android:viewportHeight="28"
+ android:width="@dimen/ps_floating_mask_corner_radius"
+ android:height="@dimen/ps_floating_mask_corner_radius">
+ <path
+ android:pathData="M0 28H28C24.3228 28 20.6821 27.2759 17.2847 25.8687C13.8877 24.4614 10.8013 22.3989 8.20117 19.7988C5.60107 17.1987 3.53857 14.1123 2.13135 10.7153C0.724121 7.31787 0 3.67725 0 0V28Z"
+ android:fillType="evenOdd"
+ android:fillColor="?attr/allAppsScrimColor" />
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_mask_right_corner.xml b/res/drawable/bg_ps_mask_right_corner.xml
new file mode 100644
index 0000000..d63b866
--- /dev/null
+++ b/res/drawable/bg_ps_mask_right_corner.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:viewportWidth="28"
+ android:viewportHeight="28"
+ android:width="@dimen/ps_floating_mask_corner_radius"
+ android:height="@dimen/ps_floating_mask_corner_radius">
+ <path
+ android:pathData="M28 28V0C28 3.67725 27.2759 7.31787 25.8687 10.7153C24.4614 14.1123 22.3989 17.1987 19.7988 19.7988C17.1987 22.3989 14.1123 24.4614 10.7153 25.8687C7.31787 27.2759 3.67725 28 0 28H28Z"
+ android:fillType="evenOdd"
+ android:fillColor="?attr/allAppsScrimColor" />
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/ic_allapps_search.xml b/res/drawable/ic_allapps_search.xml
index 0c3ab78..53b4f91 100644
--- a/res/drawable/ic_allapps_search.xml
+++ b/res/drawable/ic_allapps_search.xml
@@ -18,8 +18,9 @@
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
- android:autoMirrored="true">
+ android:autoMirrored="true"
+ android:tint="?attr/widgetPickerSearchTextColor">
<path
- android:fillColor="?attr/widgetPickerSearchTextColor"
+ android:fillColor="#FFFFFF"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
</vector>
diff --git a/res/drawable/ic_info_no_shadow.xml b/res/drawable/ic_info_no_shadow.xml
index 7c43779..29a81bd 100644
--- a/res/drawable/ic_info_no_shadow.xml
+++ b/res/drawable/ic_info_no_shadow.xml
@@ -18,7 +18,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?android:attr/textColorPrimary">
+ android:tint="?attr/materialColorOnSurface">
<path
android:fillColor="@android:color/white"
diff --git a/res/drawable/ic_install_no_shadow.xml b/res/drawable/ic_install_no_shadow.xml
index eaad0de..6e0125a 100644
--- a/res/drawable/ic_install_no_shadow.xml
+++ b/res/drawable/ic_install_no_shadow.xml
@@ -18,7 +18,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?android:attr/textColorPrimary">
+ android:tint="?attr/materialColorOnSurface">
<path
android:fillColor="@android:color/white"
diff --git a/res/drawable/ic_install_to_private.xml b/res/drawable/ic_install_to_private.xml
index 0e9833c..a16d35a 100644
--- a/res/drawable/ic_install_to_private.xml
+++ b/res/drawable/ic_install_to_private.xml
@@ -20,12 +20,18 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?android:attr/textColorPrimary">
+ android:tint="?attr/materialColorOnSurface">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M19,5V19H5V5H19ZM19,3H5C3.9,3 3,3.9 3,5V19C3,20.1 3.9,21 5,21H19C20.1,21 21,20.1 21,19V5C21,3.9 20.1,3 19,3Z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M12.93,12.27L13.5,15.5H10.5L11.07,12.27C10.43,11.94 10,11.27 10,10.5C10,9.4 10.9,8.5 12,8.5C13.1,8.5 14,9.4 14,10.5C14,11.27 13.57,11.94 12.93,12.27Z" />
+ <group>
+ <clip-path
+ android:pathData="M0,0h24v24h-24z"/>
+ <path
+ android:pathData="M12.001,1.999L4.001,4.999V11.089C4.001,16.139 7.411,20.849 12.001,21.999C16.591,20.849 20.001,16.139 20.001,11.089V4.999L12.001,1.999ZM18.001,11.089C18.001,15.089 15.451,18.789 12.001,19.919C8.551,18.789 6.001,15.099 6.001,11.089V6.389L12.001,4.139L18.001,6.389V11.089Z"
+ android:fillColor="@android:color/white"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M8.501,9.5C8.501,11.08 9.561,12.41 11.001,12.84V18H13.001V17H15.001V15H13.001V12.84C14.441,12.41 15.501,11.09 15.501,9.5C15.501,7.57 13.931,6 12.001,6C10.071,6 8.501,7.57 8.501,9.5ZM13.501,9.5C13.501,10.33 12.831,11 12.001,11C11.171,11 10.501,10.33 10.501,9.5C10.501,8.67 11.171,8 12.001,8C12.831,8 13.501,8.67 13.501,9.5Z"
+ android:fillColor="@android:color/white"
+ android:fillType="evenOdd"/>
+ </group>
</vector>
diff --git a/res/drawable/ic_plus.xml b/res/drawable/ic_plus.xml
new file mode 100644
index 0000000..d004f42
--- /dev/null
+++ b/res/drawable/ic_plus.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/widget_cell_add_button_drawable_width"
+ android:height="18dp"
+ android:viewportWidth="19"
+ android:viewportHeight="18">
+ <path
+ android:pathData="M15.5,9.75H10.25V15H8.75V9.75H3.5V8.25H8.75V3H10.25V8.25H15.5V9.75Z"
+ android:fillColor="#ffffff"/>
+</vector>
diff --git a/res/drawable/ic_private_profile_app_scroller_badge.xml b/res/drawable/ic_private_profile_app_scroller_badge.xml
new file mode 100644
index 0000000..ede42b9
--- /dev/null
+++ b/res/drawable/ic_private_profile_app_scroller_badge.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="32"
+ android:viewportHeight="32"
+ android:width="32dp"
+ android:height="32dp">
+ <path
+ android:pathData="M16.0007 2.66602L5.33398 6.66602V14.786C5.33398 21.5194 9.88065 27.7993 16.0007 29.3327C22.1207 27.7993 26.6673 21.5194 26.6673 14.786V6.66602L16.0007 2.66602ZM20.0007 19.9993V22.666H17.334V23.9993H14.6673V17.1193C12.7473 16.546 11.334 14.786 11.334 12.666C11.334 10.0927 13.4273 7.99935 16.0007 7.99935C18.574 7.99935 20.6673 10.0927 20.6673 12.666C20.6673 14.7727 19.254 16.546 17.334 17.1193V19.9993H20.0007Z"
+ android:fillType="evenOdd"
+ android:fillColor="?android:attr/textColorPrimaryInverse" />
+ <path
+ android:pathData="M16 14.666C17.1046 14.666 18 13.7706 18 12.666C18 11.5614 17.1046 10.666 16 10.666C14.8954 10.666 14 11.5614 14 12.666C14 13.7706 14.8954 14.666 16 14.666Z"
+ android:fillColor="?android:attr/textColorPrimaryInverse" />
+</vector>
diff --git a/res/drawable/ic_private_space_with_background.xml b/res/drawable/ic_private_space_with_background.xml
index 59a33dd..cb37c9a 100644
--- a/res/drawable/ic_private_space_with_background.xml
+++ b/res/drawable/ic_private_space_with_background.xml
@@ -12,18 +12,21 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:viewportWidth="48"
android:viewportHeight="48"
android:width="48dp"
android:height="48dp">
<path
android:pathData="M48 24A24 24 0 0 1 0 24A24 24 0 0 1 48 24Z"
- android:fillColor="?attr/materialColorOutlineVariant" />
+ android:fillColor="?androidprv:attr/materialColorSurfaceContainerLowest" />
<path
- android:pathData="M33.3333 14.6667V33.3333H14.6667V14.6667H33.3333ZM33.3333 12H14.6667C13.2 12 12 13.2 12 14.6667V33.3333C12 34.8 13.2 36 14.6667 36H33.3333C34.8 36 36 34.8 36 33.3333V14.6667C36 13.2 34.8 12 33.3333 12Z"
+ android:pathData="M24.0021 10.6641L13.3354 14.6641V22.7841C13.3354 29.5174 17.8821 35.7974 24.0021 37.3307C30.1221 35.7974 34.6688 29.5174 34.6688 22.7841V14.6641L24.0021 10.6641ZM32.0021 22.7841C32.0021 28.1174 28.6021 33.0507 24.0021 34.5574C19.4021 33.0507 16.0021 28.1307 16.0021 22.7841V16.5174L24.0021 13.5174L32.0021 16.5174V22.7841Z"
+ android:fillType="evenOdd"
android:fillColor="?attr/materialColorOnSurface" />
<path
- android:pathData="M25.2397 24.3597L25.9997 28.6663H21.9997L22.7597 24.3597C21.9063 23.9197 21.333 23.0263 21.333 21.9997C21.333 20.533 22.533 19.333 23.9997 19.333C25.4663 19.333 26.6663 20.533 26.6663 21.9997C26.6663 23.0263 26.093 23.9197 25.2397 24.3597Z"
+ android:pathData="M19.3354 20.6657C19.3354 22.7724 20.7488 24.5457 22.6688 25.119V31.999H25.3354V30.6657H28.0021V27.999H25.3354V25.119C27.2554 24.5457 28.6688 22.7857 28.6688 20.6657C28.6688 18.0924 26.5754 15.999 24.0021 15.999C21.4288 15.999 19.3354 18.0924 19.3354 20.6657ZM26.0021 20.6657C26.0021 21.7724 25.1088 22.6657 24.0021 22.6657C22.8954 22.6657 22.0021 21.7724 22.0021 20.6657C22.0021 19.559 22.8954 18.6657 24.0021 18.6657C25.1088 18.6657 26.0021 19.559 26.0021 20.6657Z"
+ android:fillType="evenOdd"
android:fillColor="?attr/materialColorOnSurface" />
</vector>
diff --git a/res/drawable/ic_uninstall_no_shadow.xml b/res/drawable/ic_uninstall_no_shadow.xml
index fbabdd2..6200054 100644
--- a/res/drawable/ic_uninstall_no_shadow.xml
+++ b/res/drawable/ic_uninstall_no_shadow.xml
@@ -18,7 +18,7 @@
android:height="20dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
- android:tint="?android:attr/textColorPrimary" >
+ android:tint="?attr/materialColorOnSurface" >
<path
android:fillColor="@android:color/white"
android:pathData="M15,4V3H9v1H4v2h1v13c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V6h1V4H15z M17,19H7V6h10V19z" />
diff --git a/res/drawable/page_indicator.xml b/res/drawable/page_indicator.xml
index c0ccc49..d4cb13f 100644
--- a/res/drawable/page_indicator.xml
+++ b/res/drawable/page_indicator.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="?attr/folderPaginationColor"/>
+ <solid android:color="?attr/pageIndicatorDotColor"/>
<size android:width="@dimen/page_indicator_size" android:height="@dimen/page_indicator_size"/>
</shape>
\ No newline at end of file
diff --git a/res/drawable/private_space_app_divider.xml b/res/drawable/private_space_app_divider.xml
index 7d069ef..1ea12b3 100644
--- a/res/drawable/private_space_app_divider.xml
+++ b/res/drawable/private_space_app_divider.xml
@@ -17,5 +17,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/materialColorOutlineVariant"/>
- <size android:height="1dp" />
+ <size android:height="@dimen/all_apps_divider_height" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/private_space_install_app_icon.xml b/res/drawable/private_space_install_app_icon.xml
index 4c167ba..cfec2b1 100644
--- a/res/drawable/private_space_install_app_icon.xml
+++ b/res/drawable/private_space_install_app_icon.xml
@@ -23,9 +23,9 @@
android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z" />
<path
android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z"
- android:fillColor="@color/material_color_surface_bright" />
+ android:fillColor="@color/material_color_surface_container_lowest" />
<path
android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
- android:fillColor="@color/material_color_on_surface_variant" />
+ android:fillColor="@color/material_color_on_surface" />
</group>
</vector>
diff --git a/res/drawable/ps_lock_background.xml b/res/drawable/ps_lock_background.xml
index b81c23f..0be83db 100644
--- a/res/drawable/ps_lock_background.xml
+++ b/res/drawable/ps_lock_background.xml
@@ -15,13 +15,17 @@
~ limitations under the License.
-->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:inset="4dp">
- <shape android:shape="rectangle">
- <corners android:radius="@dimen/ps_lock_corner_radius" />
- <solid android:color="?attr/materialColorPrimaryFixedDim" />
- <padding
- android:left="@dimen/ps_lock_button_background_padding"
- android:right="@dimen/ps_lock_button_background_padding" />
- </shape>
-</inset>
\ No newline at end of file
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/accent_ripple_color">
+ <item>
+ <inset android:inset="4dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/ps_lock_corner_radius" />
+ <solid android:color="?attr/materialColorPrimaryFixedDim" />
+ <padding
+ android:left="@dimen/ps_lock_button_background_padding"
+ android:right="@dimen/ps_lock_button_background_padding" />
+ </shape>
+ </inset>
+ </item>
+</ripple>
diff --git a/res/drawable/widget_cell_add_button_background.xml b/res/drawable/widget_cell_add_button_background.xml
new file mode 100644
index 0000000..860d1cd
--- /dev/null
+++ b/res/drawable/widget_cell_add_button_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners
+ android:radius="50dp"/>
+ <solid android:color="?attr/widgetPickerAddButtonBackgroundColor" />
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/widget_picker_preview_pane_scroll_thumb.xml b/res/drawable/widget_picker_preview_pane_scroll_thumb.xml
new file mode 100644
index 0000000..24f90b0
--- /dev/null
+++ b/res/drawable/widget_picker_preview_pane_scroll_thumb.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<!--
+A variation of material's scrollbar_handle_material.xml that has paddings to make it smaller.
+ScrollView's "insideInsets" / "insideOverlay" styles don't consider corner radius applied to scroll
+views, so we apply matching padding to the thumb to align it.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:bottom="@dimen/widget_list_top_bottom_corner_radius"
+ android:top="@dimen/widget_list_top_bottom_corner_radius">
+ <shape
+ android:shape="rectangle"
+ android:tint="?android:attr/colorControlNormal">
+ <solid android:color="#84ffffff" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/work_mode_fab_background.xml b/res/drawable/work_mode_fab_background.xml
index 478b887..6be33e8 100644
--- a/res/drawable/work_mode_fab_background.xml
+++ b/res/drawable/work_mode_fab_background.xml
@@ -19,9 +19,6 @@
<shape android:shape="rectangle">
<corners android:radius="@dimen/work_fab_radius" />
<solid android:color="@color/work_fab_bg_color" />
- <padding
- android:left="@dimen/work_profile_footer_padding"
- android:right="@dimen/work_profile_footer_padding" />
</shape>
</item>
</ripple>
diff --git a/res/layout/develop_options_edit_text.xml b/res/layout/develop_options_edit_text.xml
new file mode 100644
index 0000000..5e44228
--- /dev/null
+++ b/res/layout/develop_options_edit_text.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.launcher3.ExtendedEditText
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:minWidth="100dp"
+ android:inputType="numberSigned"
+ android:id="@+id/pref_edit_text"
+ android:selectAllOnFocus="true"
+ android:imeOptions="actionDone"
+ android:maxLines="1" />
\ No newline at end of file
diff --git a/res/layout/developer_options_top_bar.xml b/res/layout/developer_options_top_bar.xml
index 1b138ea..ca46311 100644
--- a/res/layout/developer_options_top_bar.xml
+++ b/res/layout/developer_options_top_bar.xml
@@ -11,16 +11,17 @@
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/developer_options_filter_margins"
+ android:background="@drawable/rounded_action_button"
+ android:layout_marginTop="4dp"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp"
+ android:padding="12dp"
+ android:drawableStart="@drawable/ic_allapps_search"
+ android:drawableTint="?android:attr/textColorSecondary"
+ android:drawablePadding="8dp"
android:hint="@string/developer_options_filter_hint"
android:inputType="text"
android:maxLines="1"
android:imeOptions="actionDone"
/>
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:text="Apply"
- android:visibility="invisible"
- android:id="@+id/flag_apply_btn" />
</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/folder_app_pair.xml b/res/layout/folder_app_pair.xml
new file mode 100644
index 0000000..acecd46
--- /dev/null
+++ b/res/layout/folder_app_pair.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.launcher3.apppairs.AppPairIcon
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:focusable="true"
+ launcher:iconDisplay="folder" >
+ <com.android.launcher3.apppairs.AppPairIconGraphic
+ android:id="@+id/app_pair_icon_graphic"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="false" />
+ <com.android.launcher3.BubbleTextView
+ style="@style/BaseIcon"
+ android:id="@+id/app_pair_icon_name"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="false"
+ android:layout_gravity="top"
+ android:textColor="?attr/folderTextColor"
+ launcher:iconDisplay="folder" />
+</com.android.launcher3.apppairs.AppPairIcon>
\ No newline at end of file
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
index 82b0b8d..d14dcd5 100644
--- a/res/layout/hotseat.xml
+++ b/res/layout/hotseat.xml
@@ -21,4 +21,6 @@
android:layout_height="match_parent"
android:theme="@style/HomeScreenElementTheme"
android:importantForAccessibility="no"
+ android:clickable="false"
+ android:longClickable="false"
launcher:containerType="hotseat" />
\ No newline at end of file
diff --git a/res/layout/predicted_app_icon.xml b/res/layout/predicted_app_icon.xml
new file mode 100644
index 0000000..52df694
--- /dev/null
+++ b/res/layout/predicted_app_icon.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.views.DoubleShadowBubbleTextView style="@style/BaseIcon.Workspace" />
diff --git a/res/layout/private_space_divider.xml b/res/layout/private_space_divider.xml
index fff8629..f72e139 100644
--- a/res/layout/private_space_divider.xml
+++ b/res/layout/private_space_divider.xml
@@ -18,8 +18,8 @@
android:importantForAccessibility="no"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingLeft="@dimen/ps_app_divider_padding"
- android:paddingRight="@dimen/ps_app_divider_padding"
+ android:paddingHorizontal="@dimen/ps_app_divider_horizontal_padding"
+ android:paddingVertical="@dimen/ps_app_divider_vertical_padding"
android:src="@drawable/private_space_app_divider"
android:scaleType="fitXY"
android:focusable="false" />
\ No newline at end of file
diff --git a/res/layout/private_space_header.xml b/res/layout/private_space_header.xml
index 0b0af87..9c0f129 100644
--- a/res/layout/private_space_header.xml
+++ b/res/layout/private_space_header.xml
@@ -24,7 +24,10 @@
android:background="@drawable/bg_ps_header"
android:clipToOutline="true"
android:gravity="center_vertical"
- android:orientation="horizontal">
+ android:textDirection="locale"
+ android:orientation="horizontal"
+ android:contentDescription="@string/ps_container_lock_button_content_description"
+ android:importantForAccessibility="yes">
<LinearLayout
android:id="@+id/settingsAndLockGroup"
@@ -33,13 +36,12 @@
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:layout_alignParentEnd="true"
- android:animateLayoutChanges="true">
- <ImageButton
+ android:animateLayoutChanges="false">
+ <com.android.launcher3.allapps.PrivateSpaceSettingsButton
android:id="@+id/ps_settings_button"
android:layout_width="@dimen/ps_header_image_height"
android:layout_height="@dimen/ps_header_image_height"
android:background="@drawable/ps_settings_background"
- android:layout_marginEnd="@dimen/ps_header_settings_icon_margin_end"
android:src="@drawable/ic_ps_settings"
android:contentDescription="@string/ps_container_settings" />
<LinearLayout
@@ -48,8 +50,8 @@
android:layout_height="@dimen/ps_header_image_height"
android:background="@drawable/ps_lock_background"
android:gravity="center_vertical"
- android:layout_marginEnd="@dimen/ps_header_layout_margin"
- android:contentDescription="@string/ps_container_lock_unlock_button">
+ android:layout_marginEnd="@dimen/ps_lock_button_margin_end"
+ android:contentDescription="@string/ps_container_lock_button_content_description">
<ImageView
android:id="@+id/lock_icon"
android:layout_width="@dimen/ps_lock_icon_size"
@@ -87,13 +89,15 @@
<TextView
android:id="@+id/ps_container_header"
android:layout_width="wrap_content"
- android:layout_height="@dimen/ps_header_text_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/ps_header_text_height"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/settingsAndLockGroup"
android:gravity="center_vertical"
android:layout_marginStart="@dimen/ps_header_layout_margin"
android:text="@string/ps_container_title"
- android:theme="@style/PrivateSpaceHeaderTextStyle"/>
+ android:theme="@style/PrivateSpaceHeaderTextStyle"
+ android:importantForAccessibility="no"/>
</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/private_space_mask_view.xml b/res/layout/private_space_mask_view.xml
new file mode 100644
index 0000000..44e2797
--- /dev/null
+++ b/res/layout/private_space_mask_view.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.launcher3.allapps.FloatingMaskView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_marginLeft="@dimen/ps_floating_mask_end_padding"
+ android:layout_marginRight="@dimen/ps_floating_mask_end_padding"
+ android:importantForAccessibility="noHideDescendants"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/left_corner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="parent"
+ android:importantForAccessibility="no"
+ android:background="@drawable/bg_ps_mask_left_corner"/>
+
+ <ImageView
+ android:id="@+id/right_corner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="centerCrop"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:importantForAccessibility="no"
+ android:background="@drawable/bg_ps_mask_right_corner"/>
+
+ <ImageView
+ android:id="@+id/bottom_box"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="@id/left_corner"
+ app:layout_constraintEnd_toEndOf="@id/right_corner"
+ app:layout_constraintTop_toBottomOf="@id/left_corner"
+ android:importantForAccessibility="no"
+ android:background="?attr/allAppsScrimColor"/>
+
+</com.android.launcher3.allapps.FloatingMaskView>
\ No newline at end of file
diff --git a/res/layout/snackbar.xml b/res/layout/snackbar.xml
index b818943..6bc1729 100644
--- a/res/layout/snackbar.xml
+++ b/res/layout/snackbar.xml
@@ -39,6 +39,7 @@
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:background="?android:attr/selectableItemBackground"
+ android:longClickable="false"
android:textStyle="bold"
android:textSize="@dimen/snackbar_max_text_size"
android:textColor="?android:attr/colorAccent"
diff --git a/res/layout/switch_preference_with_settings.xml b/res/layout/switch_preference_with_settings.xml
deleted file mode 100644
index cd51833..0000000
--- a/res/layout/switch_preference_with_settings.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center_vertical">
-
- <ImageView
- android:id="@+id/settings"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/ic_setting"
- android:forceDarkAllowed="true"
- android:padding="12dp"
- android:background="?android:attr/selectableItemBackgroundBorderless" />
-
- <View
- android:id="@+id/divider"
- android:layout_width="1dp"
- android:layout_height="30dp"
- android:layout_marginEnd="8dp"
- android:background="?android:attr/listDivider" />
-
- <!-- Note: seems we need focusable="false" and clickable="false" when moving to androidx -->
- <Switch
- android:id="@android:id/switch_widget"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@null" />
-</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 31f4870..43a8aac 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -18,6 +18,7 @@
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:defaultFocusHighlightEnabled="false"
android:orientation="vertical" >
<com.android.launcher3.folder.FolderPagedView
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 55dd1de..4533873 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -17,7 +17,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:paddingHorizontal="@dimen/widget_cell_horizontal_padding"
+ android:layout_marginStart="@dimen/widget_cell_horizontal_padding"
+ android:layout_marginEnd="@dimen/widget_cell_horizontal_padding"
android:paddingVertical="@dimen/widget_cell_vertical_padding"
android:layout_weight="1"
android:orientation="vertical"
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 0c606f6..b6412db 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -35,50 +35,79 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:layout_gravity="fill"/>
-
- <ImageView
- android:id="@+id/widget_badge"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:importantForAccessibility="no"
- android:layout_gravity="end|bottom"
- android:layout_margin="@dimen/profile_badge_margin"/>
</com.android.launcher3.widget.WidgetCellPreview>
- <!-- The name of the widget. -->
- <TextView
- android:id="@+id/widget_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:fadingEdge="horizontal"
- android:gravity="center_horizontal|center_vertical"
- android:singleLine="true"
- android:maxLines="1"
- android:textColor="?android:attr/textColorPrimary"
- android:drawablePadding="@dimen/widget_cell_app_icon_padding"
- android:textSize="@dimen/widget_cell_font_size" />
-
- <!-- The original dimensions of the widget -->
- <TextView
- android:id="@+id/widget_dims"
+ <FrameLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:textColor="?android:attr/textColorSecondary"
- android:textSize="@dimen/widget_cell_font_size"
- android:alpha="0.7" />
+ android:layout_height="wrap_content">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:id="@+id/widget_text_container"
+ android:orientation="vertical">
+ <!-- The name of the widget. -->
+ <TextView
+ android:id="@+id/widget_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_horizontal|center_vertical"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:textColor="?attr/widgetCellTitleColor"
+ android:textSize="@dimen/widget_cell_title_font_size"
+ android:textFontWeight="@integer/widget_cell_title_font_weight"
+ android:lineHeight="@dimen/widget_cell_title_line_height"
+ android:drawablePadding="@dimen/widget_cell_app_icon_padding" />
- <TextView
- android:id="@+id/widget_description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:textSize="@dimen/widget_cell_font_size"
- android:textColor="?android:attr/textColorSecondary"
- android:maxLines="2"
- android:ellipsize="end"
- android:fadingEdge="horizontal"
- android:alpha="0.7" />
+ <!-- The original dimensions of the widget -->
+ <TextView
+ android:id="@+id/widget_dims"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textColor="?attr/widgetCellSubtitleColor"
+ android:textSize="@dimen/widget_cell_dims_font_size"
+ android:textFontWeight="@integer/widget_cell_dims_font_weight"
+ android:lineHeight="@dimen/widget_cell_dims_line_height" />
-</merge>
\ No newline at end of file
+ <TextView
+ android:id="@+id/widget_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textColor="?attr/widgetCellSubtitleColor"
+ android:textSize="@dimen/widget_cell_description_font_size"
+ android:textFontWeight="@integer/widget_cell_description_font_weight"
+ android:lineHeight="@dimen/widget_cell_description_line_height"
+ android:maxLines="3"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal" />
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/widget_add_button"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/widget_cell_add_button_height"
+ android:layout_gravity="center"
+ android:minWidth="0dp"
+ android:paddingStart="@dimen/widget_cell_add_button_start_padding"
+ android:paddingEnd="@dimen/widget_cell_add_button_end_padding"
+ android:text="@string/widget_add_button_label"
+ android:textColor="?attr/widgetPickerAddButtonTextColor"
+ android:textSize="@dimen/widget_cell_add_button_font_size"
+ android:fontWeight="@integer/widget_cell_add_button_font_weight"
+ android:lineHeight="@dimen/widget_cell_add_button_line_height"
+ android:gravity="center"
+ android:visibility="gone"
+ android:drawableStart="@drawable/ic_plus"
+ android:drawablePadding="@dimen/widget_cell_add_button_drawable_padding"
+ android:drawableTint="?attr/widgetPickerAddButtonTextColor"
+ android:maxLines="1"
+ style="@style/Button.Rounded.Colored"
+ android:background="@drawable/widget_cell_add_button_background" />
+ </FrameLayout>
+</merge>
diff --git a/res/layout/widget_recommendations.xml b/res/layout/widget_recommendations.xml
index 89821ac..5879b0f 100644
--- a/res/layout/widget_recommendations.xml
+++ b/res/layout/widget_recommendations.xml
@@ -28,17 +28,21 @@
android:layout_marginTop="16dp"
android:accessibilityLiveRegion="polite"
android:gravity="center_horizontal"
+ android:layout_gravity="top"
android:lineHeight="20sp"
android:textColor="?attr/widgetPickerTitleColor"
android:textFontWeight="500"
android:textSize="16sp"
+ android:maxLines="1"
+ android:paddingHorizontal="8dp"
+ android:ellipsize="end"
android:visibility="gone" />
<!-- Shown when there are more than one pages -->
<com.android.launcher3.pageindicators.PageIndicatorDots
android:id="@+id/widget_recommendations_page_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
+ android:layout_gravity="center_horizontal|top"
android:elevation="1dp"
android:visibility="gone" />
<!--
@@ -50,8 +54,9 @@
<com.android.launcher3.widget.picker.WidgetRecommendationsView
android:id="@+id/widget_recommendations_view"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="0dp"
android:layout_gravity="center"
+ android:layout_weight="1"
android:background="@drawable/widgets_surface_background"
android:importantForAccessibility="yes"
launcher:pageIndicator="@+id/widget_recommendations_page_indicator" />
diff --git a/res/layout/widget_recommendations_table.xml b/res/layout/widget_recommendations_table.xml
index e3f0562..b53d2d5 100644
--- a/res/layout/widget_recommendations_table.xml
+++ b/res/layout/widget_recommendations_table.xml
@@ -17,5 +17,4 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingHorizontal="@dimen/widget_recommendations_table_horizontal_padding"
android:paddingVertical="@dimen/widget_recommendations_table_vertical_padding" />
diff --git a/res/layout/widgets_bottom_sheet_content.xml b/res/layout/widgets_bottom_sheet_content.xml
index 065c2ed..0a1407e 100644
--- a/res/layout/widgets_bottom_sheet_content.xml
+++ b/res/layout/widgets_bottom_sheet_content.xml
@@ -25,11 +25,10 @@
android:layout_width="@dimen/bottom_sheet_handle_width"
android:layout_height="@dimen/bottom_sheet_handle_height"
android:layout_gravity="center_horizontal"
- android:layout_marginBottom="@dimen/bottom_sheet_handle_margin"
+ android:layout_marginBottom="@dimen/widgets_bottom_sheet_handle_margin"
android:visibility="gone"
android:background="@drawable/bg_rounded_corner_bottom_sheet_handle"/>
<TextView
- style="@style/TextHeadline"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -41,8 +40,13 @@
android:id="@+id/widgets_table_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:fadeScrollbars="false"
- android:layout_marginTop="16dp">
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="24dp"
+ android:layout_marginHorizontal="@dimen/widget_bottom_sheet_horizontal_margin"
+ android:background="@drawable/widgets_surface_background"
+ android:scrollbarThumbVertical="@drawable/widget_picker_preview_pane_scroll_thumb"
+ android:clipToOutline="true"
+ android:clipChildren="true">
<include layout="@layout/widgets_table_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/res/layout/widgets_edu.xml b/res/layout/widgets_edu.xml
deleted file mode 100644
index 280c095..0000000
--- a/res/layout/widgets_edu.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<com.android.launcher3.views.WidgetsEduView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:gravity="bottom"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/edu_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/bg_rounded_corner_bottom_sheet"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:paddingHorizontal="@dimen/bottom_sheet_edu_padding"
- android:paddingTop="@dimen/bottom_sheet_edu_padding">
-
- <TextView
- style="@style/TextHeadline"
- android:id="@+id/edu_header"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:text="@string/widget_education_header"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="24sp"
- android:layout_marginBottom="16dp"/>
-
- <TextView
- android:id="@+id/edu_content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:text="@string/widget_education_content"
- android:textSize="14sp"
- android:textColor="?android:attr/textColorSecondary"
- android:layout_marginBottom="24dp"/>
-
- <Button
- android:id="@+id/edu_close_button"
- style="@style/Button.Rounded.Colored"
- android:layout_width="match_parent"
- android:layout_height="56dp"
- android:text="@string/widget_education_close_button"
- android:textSize="16sp"
- android:textColor="@color/button_text"
- android:layout_marginBottom="8dp"/>
- </LinearLayout>
-</com.android.launcher3.views.WidgetsEduView>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index 1d37043..8dc785a 100644
--- a/res/layout/widgets_full_sheet_paged_view.xml
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -79,6 +79,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
android:background="@drawable/widgets_surface_background"
android:orientation="vertical"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
@@ -92,7 +93,7 @@
android:layout_height="64dp"
android:gravity="center_horizontal"
android:orientation="horizontal"
- android:paddingVertical="8dp"
+ android:paddingBottom="8dp"
android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
android:background="?attr/widgetPickerPrimarySurfaceColor"
style="@style/TextHeadline"
diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml
index dca08ff..5427732 100644
--- a/res/layout/widgets_full_sheet_recyclerview.xml
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -29,7 +29,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/collapse_handle"
- android:paddingBottom="16dp"
+ android:paddingBottom="8dp"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:clipToOutline="true"
android:orientation="vertical">
@@ -62,6 +62,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
android:background="@drawable/widgets_surface_background"
android:orientation="vertical"
android:visibility="gone">
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index 6d26ce3..98f9dac 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -48,8 +48,10 @@
android:layout_gravity="start|center_vertical"
android:ellipsize="end"
android:maxLines="1"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="16sp"
+ android:textColor="?attr/widgetPickerHeaderAppTitleColor"
+ android:textSize="@dimen/widget_picker_header_app_title_font_size"
+ android:textFontWeight="@integer/widget_picker_header_app_title_font_weight"
+ android:lineHeight="@dimen/widget_picker_header_app_title_line_height"
tools:text="App name" />
<TextView
@@ -58,8 +60,10 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
- android:textColor="?android:attr/textColorSecondary"
- android:alpha="0.7"
+ android:textColor="?attr/widgetPickerHeaderAppSubtitleColor"
+ android:textSize="@dimen/widget_picker_header_app_subtitle_font_size"
+ android:textFontWeight="@integer/widget_picker_header_app_subtitle_font_weight"
+ android:lineHeight="@dimen/widget_picker_header_app_subtitle_line_height"
tools:text="m widgets, n shortcuts" />
</LinearLayout>
diff --git a/res/layout/widgets_list_row_header_two_pane.xml b/res/layout/widgets_list_row_header_two_pane.xml
index bdb2aed..d4baf0a 100644
--- a/res/layout/widgets_list_row_header_two_pane.xml
+++ b/res/layout/widgets_list_row_header_two_pane.xml
@@ -51,7 +51,9 @@
android:ellipsize="end"
android:maxLines="1"
android:textColor="?attr/widgetPickerHeaderAppTitleColor"
- android:textSize="16sp"
+ android:textSize="@dimen/widget_picker_header_app_title_font_size"
+ android:textFontWeight="@integer/widget_picker_header_app_title_font_weight"
+ android:lineHeight="@dimen/widget_picker_header_app_title_line_height"
android:duplicateParentState="true"
tools:text="App name" />
@@ -62,7 +64,9 @@
android:ellipsize="end"
android:maxLines="1"
android:textColor="?attr/widgetPickerHeaderAppSubtitleColor"
- android:alpha="0.7"
+ android:textSize="@dimen/widget_picker_header_app_subtitle_font_size"
+ android:textFontWeight="@integer/widget_picker_header_app_subtitle_font_weight"
+ android:lineHeight="@dimen/widget_picker_header_app_subtitle_line_height"
android:duplicateParentState="true"
tools:text="m widgets, n shortcuts" />
diff --git a/res/layout/widgets_two_pane_sheet.xml b/res/layout/widgets_two_pane_sheet.xml
index 8e45740f..bb2b7bd 100644
--- a/res/layout/widgets_two_pane_sheet.xml
+++ b/res/layout/widgets_two_pane_sheet.xml
@@ -94,42 +94,49 @@
</FrameLayout>
<FrameLayout
- android:id="@+id/right_pane_container"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="0.67"
- android:layout_marginEnd="@dimen/widget_list_horizontal_margin_two_pane"
- android:paddingTop="@dimen/widget_list_horizontal_margin_two_pane"
- android:gravity="end"
- android:layout_gravity="end"
- android:orientation="horizontal">
- <ScrollView
- android:id="@+id/right_pane_scroll_view"
+ android:layout_weight="0.67">
+ <FrameLayout
+ android:id="@+id/right_pane_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:fillViewport="true">
- <LinearLayout
- android:orientation="vertical"
+ android:layout_marginVertical="@dimen/widget_picker_vertical_margin_right_pane"
+ android:layout_marginEnd="@dimen/widget_list_horizontal_margin_two_pane"
+ android:gravity="end"
+ android:layout_gravity="end"
+ android:orientation="horizontal">
+ <ScrollView
+ android:id="@+id/right_pane_scroll_view"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:clipToOutline="true"
- android:paddingBottom="36dp"
+ android:layout_height="match_parent"
android:background="@drawable/widgets_surface_background"
- android:importantForAccessibility="yes"
- android:id="@+id/right_pane">
- <!-- Shown when there are recommendations to display -->
+ android:scrollbarThumbVertical="@drawable/widget_picker_preview_pane_scroll_thumb"
+ android:clipToOutline="true"
+ android:fillViewport="true">
<LinearLayout
- android:id="@+id/widget_recommendations_container"
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:clipToOutline="true"
+ android:paddingBottom="36dp"
android:background="@drawable/widgets_surface_background"
- android:orientation="vertical"
- android:visibility="gone">
- <include layout="@layout/widget_recommendations" />
+ android:importantForAccessibility="yes"
+ android:id="@+id/right_pane">
+ <!-- Shown when there are recommendations to display -->
+ <LinearLayout
+ android:id="@+id/widget_recommendations_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/widgets_surface_background"
+ android:orientation="vertical"
+ android:visibility="gone">
+ <include layout="@layout/widget_recommendations" />
+ </LinearLayout>
</LinearLayout>
- </LinearLayout>
- </ScrollView>
+ </ScrollView>
+ </FrameLayout>
</FrameLayout>
</LinearLayout>
</com.android.launcher3.views.SpringRelativeLayout>
diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml
index f557fb6..99db8c6 100644
--- a/res/layout/work_apps_edu.xml
+++ b/res/layout/work_apps_edu.xml
@@ -50,9 +50,9 @@
android:id="@+id/action_btn"
android:layout_width="@dimen/x_icon_size"
android:layout_height="@dimen/x_icon_size"
+ android:scaleType="centerInside"
android:layout_gravity="center"
android:contentDescription="@string/accessibility_close"
- android:padding="@dimen/x_icon_padding"
android:background="@android:color/transparent"
android:src="@drawable/ic_remove_no_shadow" />
</FrameLayout>
diff --git a/res/layout/work_mode_fab.xml b/res/layout/work_mode_fab.xml
index 276d73e..b3484c9 100644
--- a/res/layout/work_mode_fab.xml
+++ b/res/layout/work_mode_fab.xml
@@ -24,12 +24,15 @@
android:background="@drawable/work_mode_fab_background"
android:forceHasOverlappingRendering="false"
android:contentDescription="@string/work_apps_pause_btn_text"
+ android:paddingStart="@dimen/work_mode_fab_background_start_padding"
+ android:paddingEnd="@dimen/work_mode_fab_background_end_padding"
android:animateLayoutChanges="true">
<ImageView
android:id="@+id/work_icon"
android:layout_width="@dimen/work_fab_icon_size"
android:layout_height="@dimen/work_fab_icon_size"
android:importantForAccessibility="no"
+ android:layout_marginEnd="@dimen/work_fab_icon_end_margin"
android:src="@drawable/ic_corp_off"
android:tint="@color/work_fab_icon_color"
android:scaleType="center"/>
@@ -43,7 +46,7 @@
android:includeFontPadding="false"
android:textDirection="locale"
android:text="@string/work_apps_pause_btn_text"
- android:layout_marginStart="@dimen/work_fab_text_start_margin"
+ android:layout_marginEnd="@dimen/work_fab_text_end_margin"
android:ellipsize="end"
android:maxLines="1"
style="@style/TextHeadline"/>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 262038d..1a8f8e2 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Legstukke gedeaktiveer in Veiligmodus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Kortpad is nie beskikbaar nie"</string>
<string name="home_screen" msgid="5629429142036709174">"Tuis"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Stel <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as verstektuisskermapp in Instellings"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Verdeelde skerm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Programinligting vir %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Gebruikinstellings vir %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Stoor apppaar"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Hierdie apppaar word nie op hierdie toestel gesteun nie"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Vou die toestel oop om hierdie apppaar te gebruik"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Apppaar is nie beskikbaar nie"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Raak en hou om \'n legstuk te skuif."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dubbeltik en hou om \'n legstuk te skuif of gebruik gepasmaakte handelinge."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breed by %2$d hoog"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>-legstuk"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>-legstuk, %2$d wyd en %3$d hoog"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Raak en hou die legstuk om dit op die tuisskerm rond te beweeg"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Voeg by tuisskerm"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>-legstuk by tuisskerm gevoeg"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Voorstelle"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Jou daaglikse noodsaaklikhede"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Nuus vir jou"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Jou ontspansone"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Bereik jou fiksheiddoelwitte"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Spring die weer voor"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Jy hou dalk ook van"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Noodsaaklikhede"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Nuus en tydskrifte"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Vermaak"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sosiaal"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Voorgestel vir jou"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-legstukke aan die regterkant, soektog en opsies aan die linkerkant"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# legstuk}other{# legstukke}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# kortpad}other{# kortpaaie}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Werk"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Gesprekke"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Neem notas"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Nuttige inligting binne jou bereik"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Jy kan legstukke by jou tuisskerm voeg om inligting te kry sonder om programme oop te maak"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Voeg by"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Voeg <xliff:g id="WIDGET_NAME">%1$s</xliff:g>-legstuk by"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tik om legstukinstellings te verander"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Het dit"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Verander legstukinstellings"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Deursoek programme"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Laai tans programme …"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deïnstalleer"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Appinligting"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Installeer privaat"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Deïnstalleer app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Installeer"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Moenie voorstel nie"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Vasspeldvoorspelling"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installeer tans; <xliff:g id="PROGRESS">%2$s</xliff:g> voltooi"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> laai tans af, <xliff:g id="PROGRESS">%2$s</xliff:g> voltooid"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> wag tans om te installeer"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> is geargiveer. Tik om af te laai."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> is geargiveer. Tik om af te laai en terug te stel."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Programopdatering word vereis"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Die program vir hierdie ikoon is nie opgedateer nie. Jy kan dit handmatig opdateer om hierdie kortpad weer te aktiveer, of die ikoon verwyder."</string>
<string name="dialog_update" msgid="2178028071796141234">"Dateer op"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Misluk: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privaat ruimte"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Hou privaat apps gesluit en versteek"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Tik om op te stel of oop te maak"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privaat"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Privaat Ruimte-instellings"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Sluit/ontsluit Privaat Ruimte"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privaat, ontsluit."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privaat, gesluit."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Sluit"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Privaat Ruimte-oorgang"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Installeer apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Installeer"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Installeer apps in privaat ruimte"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Oorvloei"</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index e404506..80447c5 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -27,27 +27,30 @@
<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>ን እንደ ነባሪ የHome መተግበሪያ ያቀናብሩ"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"የተከፈለ ማያ ገፅ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"የመተግበሪያ መረጃ ለ%1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"የ%1$s የአጠቃቀም ቅንብሮች"</string>
<string name="save_app_pair" msgid="5647523853662686243">"የመተግበሪያ ጥምረትን ያስቀምጡ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ይህ የመተግበሪያ ጥምረት በዚህ መሣሪያ ላይ አይደገፍም"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ምግብር ወደ መነሻ ማያ ገፅ ታክሏል"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"የአስተያየት ጥቆማዎች"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"የእርስዎ ዕለታዊ መሠረታዊ ነገሮች"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"ዜና ለእርስዎ"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"የሚያርፉበት ቦታዎ"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"የአካል ብቃት ግቦችዎን ያሳኩ"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"ለአየር ሁኔታው አስቀድመው ያቅዱ"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"ይህንንም ሊወዱት ይችላሉ"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ጠቃሚ ነገሮች"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ዜና እና መጽሔቶች"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"መዝናኛ"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"ማህበራዊ"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"ለእርስዎ የተጠቆሙ"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ምግብሮች በቀኝ በኩል፣ ፍለጋ እና አማራጮች በግራ በኩል"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# ምግብር}one{# ምግብሮች}other{# ምግብሮች}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# አቋራጭ}one{# አቋራጭ}other{# አቋራጮች}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ሥራ"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"ውይይቶች"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"የማስታወሻ አያያዝ"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"በጣቶችዎ ጫፎች ላይ ጠቃሚ መረጃ"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"መተግበሪያዎችን ሳይከፍቱ መረጃ ለማግኘት በመነሻ ማያ ገጽዎ ላይ ምግብሮችን ማከል ይችላሉ"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"አክል"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"ምግብር <xliff:g id="WIDGET_NAME">%1$s</xliff:g>ን አክል"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"የምግብር ቅንብሮችን ለመለወጥ መታ ያድርጉ"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"ገባኝ"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"አራግፍ"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"የመተግበሪያ መረጃ"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"በግል ይጫኑ"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"መተግበሪያን አራግፍ"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ጫን"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"መተግበሪያውን አይጠቁሙ"</string>
<string name="pin_prediction" msgid="4196423321649756498">"የፒን ግምት"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> በመጫን ላይ፣ <xliff:g id="PROGRESS">%2$s</xliff:g> ተጠናቅቋል"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> በመውረድ ላይ፣ <xliff:g id="PROGRESS">%2$s</xliff:g> ተጠናቋል"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ለመጫን በመጠበቅ ላይ"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> በማህደር ተቀምጧል። ለማውረድ መታ ያድርጉ።"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> በማህደር ተቀምጧል። ለማወረድ እና ወደነበረበት ለመመለስ መታ ያድርጉ።"</string>
<string name="dialog_update_title" msgid="114234265740994042">"መተግበሪያ ማዘመን አስፈላጊ ነው"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"የዚህ አዶ መተግበሪያ አልተዘመነም። ይህን አቋራጭ ዳግም ለማንቃት በራስዎ ማዘመን ወይም አዶውን ማስወገድ ይችላሉ።"</string>
<string name="dialog_update" msgid="2178028071796141234">"አዘምን"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"አጣራ"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"አልተሳካም፦ <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"የግል ቦታ"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"የግል መተግበሪያዎች እንደተቆለፉ እና እንደተበቁ እንዲቆዩ ያድርጉ"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"ለማዋቀር ወይም ለመክፈት መታ ያድርጉ"</string>
<string name="ps_container_title" msgid="4391796149519594205">"የግል"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"የግል ቦታ ቅንብሮች"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"የግል ቦታን ቆልፍ/ክፈት"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"የግል፣ የተከፈተ።"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"የግል፣ የተቆለፈ።"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ቆልፍ"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"የግል ቦታ ሽግግር"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"መተግበሪያዎችን ይጫኑ"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ይጫኑ"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"መተግበሪያዎችን ወደ የግል ቦታ ይጫኑ"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ትርፍ ፍሰት"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 618e994..4eee121 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"تقسيم الشاشة"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"معلومات تطبيق %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"إعدادات استخدام \"%1$s\""</string>
<string name="save_app_pair" msgid="5647523853662686243">"حفظ استخدام التطبيقين معًا"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"لا يمكن استخدام هذين التطبيقَين في الوقت نفسه على هذا الجهاز"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"تمت إضافة الأداة <xliff:g id="WIDGET_NAME">%1$s</xliff:g> إلى الشاشة الرئيسية."</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"اقتراحات"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"تطبيقات أساسية للحياة اليومية"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"أخبار مقترَحة لك"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"محتوى ترفيهي مقترَح"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"تحقيق أهداف اللياقة البدنية"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"معرفة حالة الطقس أولاً بأول"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"محتوى قد يعجبك أيضًا"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"الأساسيات"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"الأخبار والمجلات"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"الترفيه"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"التواصل الاجتماعي"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"اقتراحاتنا لك"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"تطبيقات \"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>\" المصغّرة على اليسار، والبحث والخيارات على اليمين"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{تطبيق مصغّر واحد}zero{# تطبيق مصغّر}two{تطبيقان مصغّران}few{# تطبيقات مصغّرة}many{# تطبيقًا مصغّرًا}other{# تطبيق مصغّر}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{اختصار واحد}zero{# اختصار}two{اختصاران}few{# اختصارات}many{# اختصارًا}other{# اختصار}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"تطبيقات العمل"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"المحادثات"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"تدوين الملاحظات"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"معلومات مفيدة في متناول يديك"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"للحصول على معلومات بدون فتح التطبيقات، يمكنك إضافة التطبيقات المصغّرة إلى الشاشة الرئيسية."</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"إضافة"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"إضافة التطبيق المصغّر \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"انقر لتغيير إعدادات الأداة"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"حسنًا"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"إلغاء التثبيت"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"معلومات عن التطبيق"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"تثبيت في مساحة خاصّة"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"إلغاء تثبيت التطبيق"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"تثبيت"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"عدم اقتراح التطبيق"</string>
<string name="pin_prediction" msgid="4196423321649756498">"تثبيت التطبيق المتوقّع"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"جارٍ تثبيت <xliff:g id="NAME">%1$s</xliff:g>، مستوى التقدم: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"جارٍ تنزيل <xliff:g id="NAME">%1$s</xliff:g>، اكتمل <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> في انتظار التثبيت"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"تمت أرشفة تطبيق <xliff:g id="NAME">%1$s</xliff:g>. انقر للتنزيل."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"تمت أرشفة تطبيق \"<xliff:g id="NAME">%1$s</xliff:g>\". انقر لتنزيله واستعادته."</string>
<string name="dialog_update_title" msgid="114234265740994042">"مطلوب تحديث التطبيق"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"لم يتمّ تحديث التطبيق الخاص بهذا الرمز. يمكنك تحديث التطبيق يدويًا لإعادة تفعيل هذا الاختصار أو إزالة الرمز."</string>
<string name="dialog_update" msgid="2178028071796141234">"تحديث"</string>
@@ -169,7 +172,7 @@
<string name="accessibility_close" msgid="2277148124685870734">"إغلاق"</string>
<string name="all_apps_personal_tab" msgid="4190252696685155002">"شخصية"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"للعمل"</string>
- <string name="work_profile_toggle_label" msgid="3081029915775481146">"الملف الشخصي للعمل"</string>
+ <string name="work_profile_toggle_label" msgid="3081029915775481146">"ملف العمل"</string>
<string name="work_profile_edu_work_apps" msgid="7895468576497746520">"تحمل تطبيقات العمل مميّزة بشارة ومرئية لمشرف تكنولوجيا المعلومات."</string>
<string name="work_profile_edu_accept" msgid="6069788082535149071">"حسنًا"</string>
<string name="work_apps_paused_title" msgid="3040901117349444598">"تطبيقات العمل متوقفة مؤقتًا"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"فلتر"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"تعذَّر <xliff:g id="WHAT">%1$s</xliff:g>."</string>
<string name="private_space_label" msgid="2359721649407947001">"مساحة خاصة"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"إبقاء التطبيقات الخاصة مقفلة ومخفية"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"انقر للإعداد أو الفتح"</string>
<string name="ps_container_title" msgid="4391796149519594205">"المساحة الخاصة"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"إعدادات المساحة الخاصة"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"قفل المساحة الخاصة أو فتح قفلها"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"المساحة الخاصة غير مُقفلة."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"المساحة الخاصة مُقفلة."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"قفل"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"النقل إلى المساحة الخاصة"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"تثبيت التطبيقات"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"تثبيت"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"تثبيت التطبيقات في المساحة الخاصّة"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"القائمة الكاملة"</string>
</resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 0abdd36..52ec7ea 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"বিভাজিত স্ক্ৰীন"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sৰ বাবে এপৰ তথ্য"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sৰ বাবে ব্যৱহাৰৰ ছেটিং"</string>
<string name="save_app_pair" msgid="5647523853662686243">"এপৰ পেয়াৰ ছেভ কৰক"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"এই ডিভাইচটোত এই এপ্ পেয়াৰ কৰাৰ সুবিধাটো সমৰ্থিত নহয়"</string>
<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="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d বহল x %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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ৱিজেটটো গৃহ স্ক্ৰীনত যোগ দিয়া হৈছে"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"পৰামৰ্শ"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"আপোনাৰ দৈনিক অত্যাৱশ্যকীয় সামগ্ৰী"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"আপোনাৰ বাবে বাতৰি"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"আপোনাৰ পচন্দৰ স্থান"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"আপোনাৰ সুস্থতাৰ লক্ষ্যত উপনীত হওক"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"বতৰৰ বিষয়ে আগতীয়াকৈ জানক"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"আপুনি হয়তো এইটোও পচন্দ কৰিব"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"অত্যাৱশ্যকীয়সমূহ"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"বাতৰি আৰু আলোচনী"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"মনোৰঞ্জন"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"সামাজিক"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"আপোনাৰ বাবে পৰামৰ্শ হিচাপে আগবঢ়োৱা"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ৱিজেট সোঁফালে, সন্ধান আৰু বিকল্পসমূহ বাওঁফালে"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# টা ৱিজেট}one{# টা ৱিজেট}other{# টা ৱিজেট}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# টা শ্বৰ্টকাট}one{# টা শ্বৰ্টকাট}other{# টা শ্বৰ্টকাট}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"কৰ্মস্থান"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"বাৰ্তালাপ"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"টোকা গ্ৰহণ কৰা"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"আপোনাৰ আঙুলিৰে টিপতে উপযোগী তথ্য পাওক"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"এপ্ নোখোলাকৈ তথ্য পাবলৈ আপুনি নিজৰ গৃহ স্ক্ৰীনত ৱিজেট যোগ দিব পাৰে"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"যোগ দিয়ক"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ৱিজেট যোগ দিয়ক"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ৱিজেটৰ ছেটিং সলনি কৰিবলৈ টিপক"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"বুজি পালোঁ"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"আনইনষ্টল কৰক"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"এপ্ সম্পৰ্কীয় তথ্য"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"গোপনে ইনষ্টল কৰক"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"এপ্ আনইনষ্টল কৰক"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ইনষ্টল কৰক"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"পৰামৰ্শ নিদিব"</string>
<string name="pin_prediction" msgid="4196423321649756498">"পূৰ্বানুমান কৰা এপ্টো পিন কৰক"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ইনষ্টল কৰি থকা হৈছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূৰ্ণ হৈছে"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ডাউনল’ড কৰি থকা হৈছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূৰ্ণ হ’ল"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ইনষ্টল হোৱালৈ অপেক্ষা কৰি থকা হৈছে"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> আৰ্কাইভ কৰা হৈছে। ডাউনল’ড কৰিবলৈ টিপক।"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> আৰ্কাইভ কৰা হৈছে। ডাউনল’ড আৰু পুনঃস্থাপন কৰিবলৈ টিপক।"</string>
<string name="dialog_update_title" msgid="114234265740994042">"এপ্টো আপডে’ট কৰা প্ৰয়োজন"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"এই চিহ্নটোৰ এপ্টো আপডে’ট কৰা হোৱা নাই। আপুনি এই শ্বৰ্টকাটটো পুনৰ সক্ষম কৰিবলৈ মেনুৱেলী আপডে’ট কৰিব পাৰে অথবা চিহ্নটো আঁতৰাব পাৰে।"</string>
<string name="dialog_update" msgid="2178028071796141234">"আপডে’ট কৰক"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ফিল্টাৰ"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"বিফল: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"প্ৰাইভেট স্পে\'চ"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"ব্যক্তিগত এপ্সমূহ লক কৰি লুকুৱাই ৰাখক"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"ছেট আপ কৰিবলৈ টিপক অথবা খোলক"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ব্যক্তিগত"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"ব্যক্তিগত স্পে’চৰ ছেটিং"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"প্ৰাইভেট স্পে\'চ লক/আনলক কৰক"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ব্যক্তিগত, আনলক কৰা আছে।"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ব্যক্তিগত, লক কৰা আছে।"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"লক কৰক"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"ব্যক্তিগত স্পে’চৰ স্থানান্তৰণ"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"এপ্ ইনষ্টল কৰক"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ইনষ্টল কৰক"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"এপ্সমূহ প্ৰাইভেট স্পেচত ইনষ্টল কৰক"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"অ’ভাৰফ্ল’"</string>
</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index d11c4a7..6c1cc46 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidcetlər Güvənli rejimdə deaktiv edilib"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Qısayol əlçatan deyil"</string>
<string name="home_screen" msgid="5629429142036709174">"Əsas səhifə"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ayarlarda <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> tətbiqini defolt əsas ekran tətbiqi kimi ayarlayın"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekran bölünməsi"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilə bağlı tətbiq məlumatı"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s üzrə istifadə ayarları"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Tətbiq cütünü saxlayın"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Bu tətbiq cütü bu cihazda dəstəklənmir"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Bu tətbiq cütündən istifadə üçün cihazı açın"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Tətbiq cütü əlçatan deyil"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Vidceti daşımaq üçün toxunub saxlayın."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Vidceti daşımaq üçün iki dəfə toxunub saxlayın və ya fərdi əməliyyatlardan istifadə edin."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%2$d hündürlük %1$d enində"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidceti"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidceti, eni - %2$d, hündürlüyü - %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Əsas ekranda hərəkət etdirmək üçün vidcetə toxunub saxlayın"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Əsas ekrana əlavə edin"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidceti əsas ekrana əlavə edildi"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Təkliflər"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Gündəlik vacib vidcetlər"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Sizin üçün xəbərlər"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"İstirahət zonası"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Fitnes hədəflərinə nail olun"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Hava barədə məlumatlı olun"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Tövsiyələr"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Əsaslar"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Xəbər və jurnallar"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Əyləncə"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sosial"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Təklif edirik"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> vidcetləri sağda, axtarış və seçimlər solda"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# vidcet}other{# vidcet}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# qısayol}other{# qısayol}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"İş"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Söhbətlər"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Qeydgötürmə"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Faydalı məlumatlar barmaqlarınızın ucunda"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Tətbiqləri açmadan məlumat almaq üçün Əsas ekrana vidcet əlavə edə bilərsiniz"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Əlavə edin"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidcet əlavə edin"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Vidcet ayarlarını dəyişmək üçün toxunun"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Anladım"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Vidcet ayarlarını dəyişin"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Tətbiqləri axtarın"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Tətbiqlər yüklənir…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Sistemdən sil"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Tətbiq haqqında"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Məxfi quraşdırın"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Tətbiqi sistemdən silin"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Quraşdırın"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Tətbiq təklif olunmasın"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Proqnozlaşdırılan tətbiqi bərkidin"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> quraşdırır, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlanıb"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> endirilir, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlandı"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> yüklənmək üçün gözləyir"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> arxivləndi. Endirmək üçün toxunun."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> arxivləndi. Toxunaraq endirin və bərpa edin."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Tətbiqin güncəllənməsi tələb edilir"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Bu ikona üçün tətbiq güncəllənməyib. Bu qısayolu yenidən aktivləşdirmək üçün manual olaraq güncəlləyə və ya ikonanı silə bilərsiniz."</string>
<string name="dialog_update" msgid="2178028071796141234">"Güncəlləyin"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Davam etdirin"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtr"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Alınmadı: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Şəxsi yer"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Şəxsi tətbiqləri kilidli və gizli saxlayın"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Məxfi sahə"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Toxunaraq ayarlayın və ya açın"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Şəxsi"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Şəxsi məkan ayarları"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Şəxsi məkanı kilidləyin/kiliddən çıxarın"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Şəxsi, kilidli deyil."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Şəxsi, kilidli."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Kilidləyin"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Şəxsi məkana keçid"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Tətbiqlər quraşdırın"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Quraşdırın"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Tətbiqləri şəxsi sahədə quraşdırın"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Kənara çıxma"</string>
</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index a3b80a6..24328cf 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u Bezbednom režimu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
<string name="home_screen" msgid="5629429142036709174">"Početni ekran"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Podesite <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kao podrazumevanu početnu aplikaciju u Podešavanjima"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podeljeni ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji za: %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Podešavanja potrošnje za %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Sačuvaj par aplikacija"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ovaj par aplikacija nije podržan na ovom uređaju"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Otvorite uređaj da biste koristili ovaj par aplikacija"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Par aplikacija nije dostupan"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Dodirnite i zadržite radi pomeranja vidžeta."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvaput dodirnite i zadržite da biste pomerali vidžet ili koristite prilagođene radnje."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"širina od %1$d i visina od %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidžet"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidžet, širina %2$d i visina %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Dodirnite i zadržite vidžet da biste ga pomerali po početnom ekranu"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Dodaj na početni ekran"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Dodali ste vidžet <xliff:g id="WIDGET_NAME">%1$s</xliff:g> na početni ekran"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Predlozi"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Osnovni vidžeti za svaki dan"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Vesti za vas"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Zona za opuštanje"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Ostvarite fitnes ciljeve"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Budite u toku sa vremenskim prilikama"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Možda će vam se dopasti i"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Osnovno"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Novosti i časopisi"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Zabava"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Društvene mreže"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Predloženo za vas"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Vidžeti <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> sa desne strane, pretraga i opcije sa leve strane"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# vidžet}one{# vidžet}few{# vidžeta}other{# vidžeta}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# prečica}one{# prečica}few{# prečice}other{# prečica}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Konverzacije"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Pravljenje beležaka"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Korisne informacije nadohvat ruke"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Da biste pronašli informacije bez otvaranja aplikacija, možete da dodate vidžete na početni ekran"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodajte vidžet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Dodirnite da biste promenili podešavanja vidžeta"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Važi"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Promenite podešavanja vidžeta"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Pretražite aplikacije"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Aplikacije se učitavaju…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstaliraj"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Podaci o aplikaciji"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instaliraj na privatni"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Deinstalirajte aplikaciju"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instaliraj"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne predlaži aplikaciju"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Zakači predviđanje"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> se instalira, <xliff:g id="PROGRESS">%2$s</xliff:g> gotovo"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se preuzima, završeno je <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> čeka na instaliranje"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Aplikacija <xliff:g id="NAME">%1$s</xliff:g> je arhivirana. Dodirnite da biste je preuzeli."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Aplikacija <xliff:g id="NAME">%1$s</xliff:g> je arhivirana. Dodirnite da biste je preuzeli i vratili."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Treba da ažurirate aplikaciju"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Aplikacija za ovu ikonu nije ažurirana. Možete da je ručno ažurirate da biste ponovo omogućili ovu prečicu ili uklonite ikonu."</string>
<string name="dialog_update" msgid="2178028071796141234">"Ažuriraj"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Nije uspelo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privatni prostor"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Neka privatne aplikacije budu zaključane i sakrivene"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Dodirnite da biste podesili ili otvorili"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privatno"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Podešavanja privatnog prostora"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Zaključaj/otključaj privatni prostor"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privatno, otključano."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privatno, zaključano."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Zaključavanje"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Prenos privatnog prostora"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instalirajte aplikacije"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalirajte"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instaliraj aplikacije u privatan prostor"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Preklopno"</string>
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index c978425..ebbb378 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Падзелены экран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Інфармацыя пра праграму для: %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s: налады выкарыстання"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Захаваць спалучэнне праграм"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Дадзенае спалучэнне праграм не падтрымліваецца на гэтай прыладзе"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Віджэт \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\" дададзены на галоўны экран"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Прапановы"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Праграмы першай неабходнасці"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Навіны для вас"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Зона адпачынку"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Вашы фітнэс-мэты"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Прагноз надвор\'я"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Іншыя рэкамендацыі"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Асноўнае"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Навіны і часопісы"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Забавы"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Сацыяльныя сеткі"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Рэкамендавана для вас"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Віджэты праграмы \"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>\" справа, пошук і параметры злева"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# віджэт}one{# віджэт}few{# віджэты}many{# віджэтаў}other{# віджэта}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# ярлык}one{# ярлык}few{# ярлыкі}many{# ярлыкоў}other{# ярлыка}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Працоўныя"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Размовы"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Стварэнне нататак"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Карысная інфармацыя ў вас пад рукой"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Каб не адкрываць праграмы для прагляду патрэбнай інфармацыі, дадайце віджэты на галоўны экран"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Дадаць"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Дадаць віджэт \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Націсніце, каб змяніць налады віджэта"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Зразумела"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Дэінсталяваць"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Звесткі аб праграме"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Усталяваць прыватна"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Выдаліць праграму"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Усталяваць"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Не прапаноўваць праграму"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Замацаваць прапанаваную праграму"</string>
@@ -118,7 +121,7 @@
<string name="allow_rotation_title" msgid="7222049633713050106">"Дазволіць паварот галоўнага экрана"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Пры павароце тэлефона"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"Значкі апавяшчэнняў"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"Укл."</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"Уключана"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Выкл."</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"Патрабуецца доступ да апавяшчэнняў"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Каб паказваліся значкі апавяшчэнняў, уключыце апавяшчэнні праграм для <xliff:g id="NAME">%1$s</xliff:g>"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Усталёўваецца праграма \"<xliff:g id="NAME">%1$s</xliff:g>\", завершана <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Ідзе спампоўка <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> завершана"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> чакае ўсталёўкі"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Праграма \"<xliff:g id="NAME">%1$s</xliff:g>\" знаходзіцца ў архіве. Націсніце, каб спампаваць."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Праграма \"<xliff:g id="NAME">%1$s</xliff:g>\" знаходзіцца ў архіве. Націсніце, каб спампаваць яе і аднавіць."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Неабходна абнавіць праграму"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Гэта версія праграмы састарэла. Абнавіце праграму ўручную, каб зноў карыстацца гэтым ярлыком, або выдаліце значок."</string>
<string name="dialog_update" msgid="2178028071796141234">"Абнавіць"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Актываваць"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Фільтр"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Не ўдалося: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Прыватная вобласць"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Схавайце прыватныя праграмы ў асобную прастору і закрыйце доступ да яе"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Прыватная прастора"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Націсніце, каб наладзіць або адкрыць"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Прыватная"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Налады прыватнай вобласці"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Заблакіраваць (разблакіраваць) прыватную вобласць"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Прыватная прастора, разблакіравана."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Прыватная прастора, заблакіравана."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Заблакіраваць"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Пераход у прыватную вобласць"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Усталяваць праграмы"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Усталяваць"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Усталяваць праграмы ў прыватнай прасторы"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Дадатковае меню"</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 769c538..d5d948e 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделен екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Информация за приложението за %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки за използването на %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Запазване на двойката приложения"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Тази двойка приложения не се поддържа на устройството"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Приспособлението <xliff:g id="WIDGET_NAME">%1$s</xliff:g> е добавено към началния екран"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Предложения"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Основните за деня ви"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Новини за вас"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Зоната ви за разпускане"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Постигнете фитнес целите си"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Бъдете една крачка напред с прогнозата за времето"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Може също да харесате"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Приспособления, които трябва да изпробвате"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Новини и списания"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Развлечения"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Социални мрежи"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Предложено за вас"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Приспособленията за <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> са отдясно, търсенето и опциите – отляво"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# приспособление}other{# приспособления}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# пряк път}other{# преки пътя}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Служебни"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Разговори"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Водене на бележки"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Лесен достъп до полезна информация"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"За да получавате информация, без да отваряте приложенията, можете да добавите приспособления към началния екран"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Добавяне"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Добавяне на приспособлението „<xliff:g id="WIDGET_NAME">%1$s</xliff:g>“"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Докоснете, за да промените настройките на приспособлението"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Разбрах"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Деинсталиране"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Информация за прилож."</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Инстал. в частно простр."</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Деинсталиране на приложението"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Инсталиране"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Без предлагане на приложение"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Фиксиране на предвиждането"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> се инсталира, <xliff:g id="PROGRESS">%2$s</xliff:g> завършено"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> се изтегля. Завършено: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> изчаква инсталиране"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Приложението <xliff:g id="NAME">%1$s</xliff:g> е архивирано. Докоснете за изтегляне."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Приложението <xliff:g id="NAME">%1$s</xliff:g> е архивирано. Докоснете за изтегляне и възстановяване."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Изисква се актуализация на приложението"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Приложението за тази икона не е актуализирано. Можете да го актуализирате ръчно, за да активирате отново този пряк път, или да премахнете иконата."</string>
<string name="dialog_update" msgid="2178028071796141234">"Актуализиране"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Отмяна на паузата"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Филтър"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Неуспешно: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Лично пространство"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Заключване и скриване на частните приложения"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Частно пространство"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Докоснете за настройване или отваряне"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Лично"</string>
- <string name="ps_container_settings" msgid="6059734123353320479">"Настройки за личното пространство"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Заключване/отключване на личното пространство"</string>
+ <string name="ps_container_settings" msgid="6059734123353320479">"Настройки за частното пространство"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Частно, отключено."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Частно, заключено."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Заключване"</string>
- <string name="ps_container_transition" msgid="8667331812048014412">"Преминаване към личното пространство"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Инсталиране на приложения"</string>
+ <string name="ps_container_transition" msgid="8667331812048014412">"Преминаване към частното пространство"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Инсталиране"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Инсталиране на приложения в частно пространство"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Препълване"</string>
</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index cc6a8aa..cf75fb58 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"স্প্লিট স্ক্রিন"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-এর জন্য অ্যাপ সম্পর্কিত তথ্য"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s-এর জন্য ব্যবহারের সেটিংস"</string>
<string name="save_app_pair" msgid="5647523853662686243">"অ্যাপ পেয়ার সেভ করুন"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"এই ডিভাইসে এই অ্যাপ পেয়ারটি কাজ করে না"</string>
<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="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%2$d উচ্চতা অনুযায়ী %1$d প্রস্থ"</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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> উইজেট হোম স্ক্রিনে যোগ করা হয়েছে"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"সাজেশন"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"আপনার নিত্য প্রয়োজনীয় জিনিস"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"আপনার জন্য খবর"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"আপনার চিল জোন"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"আপনার ফিটনেস সংক্রান্ত লক্ষ্যে পৌঁছান"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"আবহাওয়া সম্পর্কে আগেই খবর পান"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"আপনার এগুলিও পছন্দ হতে পারে"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"প্রয়োজনীয় জিনিস"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"খবর ও ম্যাগাজিন"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"বিনোদন"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"সোশ্যাল"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"আপনার জন্য সাজেস্ট করা হয়েছে"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> উইজেট ডানদিকে, সার্চ ও বিকল্প বাঁদিকে রয়েছে"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{#টি উইজেট}one{#টি উইজেট}other{#টি উইজেট}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{#টি শর্টকাট}one{#টি শর্টকাট}other{#টি শর্টকাট}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"অফিস"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"কথোপকথন"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"নোট নেওয়া"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"সহজেই দরকারি তথ্য পান"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"অ্যাপ না খুলেই তথ্য পাওয়ার জন্য, হোম স্ক্রিনে উইজেট যোগ করতে পারবেন"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"যোগ করুন"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> উইজেট যোগ করুন"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"উইজেট সেটিংস পরিবর্তন করতে ট্যাপ করুন"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"বুঝেছি"</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>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"সরান"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"আনইনস্টল করুন"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"অ্যাপের তথ্য"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ব্যক্তিগত প্রোফাইলে ইনস্টল করুন"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"প্রাইভেট প্রোফাইলে ইনস্টল করুন"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"অ্যাপ আনইনস্টল করুন"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ইনস্টল করুন"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"অ্যাপ সাজেস্ট করবেন না"</string>
<string name="pin_prediction" msgid="4196423321649756498">"আপনার প্রয়োজন হতে পারে এমন অ্যাপ পিন করুন"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ইনস্টল করা হচ্ছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূর্ণ হয়েছে"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ডাউনলোড হচ্ছে <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পন্ন হয়েছে"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ইনস্টলের অপেক্ষায় রয়েছে"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> আর্কাইভ করা হয়েছে। ডাউনলোড করতে ট্যাপ করুন।"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> আর্কাইভ করা হয়েছে। ডাউনলোড করতে এবং ফিরিয়ে আনতে ট্যাপ করুন।"</string>
<string name="dialog_update_title" msgid="114234265740994042">"অ্যাপটি আপডেট করা প্রয়োজন"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"এই আইকনের জন্য অ্যাপটি আপডেট করা নেই। এই শর্টকার্ট আবার চালু করতে, আপনি ম্যানুয়ালি আপডেট করতে বা সরিয়ে দিতে পারবেন।"</string>
<string name="dialog_update" msgid="2178028071796141234">"আপডেট করুন"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ফিল্টার"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"কাজটি করা যায়নি: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"ব্যক্তিগত স্পেস"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"ব্যক্তিগত অ্যাপ লক করে লুকিয়ে রাখুন"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"সেট-আপ করতে বা খুলতে ট্যাপ করুন"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ব্যক্তিগত"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"ব্যক্তিগত স্পেসের সেটিংস"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"ব্যক্তিগত স্পেস লক/আনলক করুন"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ব্যক্তিগত, আনলক করা আছে।"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ব্যক্তিগত, লক করা আছে।"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"লক"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"ব্যক্তিগত স্পেস ট্রানজিট করা"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"অ্যাপ ইনস্টল করুন"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ইনস্টল করুন"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"প্রাইভেট স্পেসে অ্যাপ ইনস্টল করুন"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ওভারফ্লো"</string>
</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 50f7a2e..1758c39 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u sigurnom načinu rada."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
<string name="home_screen" msgid="5629429142036709174">"Početni ekran"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Postavite <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kao zadanu aplikaciju za početni ekran u Postavkama"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke korištenja za: %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Sačuvaj par aplikacija"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Par aplikacija nije podržan na uređaju"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Otklopite uređaj da koristite ovaj par aplikacija"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Par aplikacija nije dostupan"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Dodirnite i zadržite da pomjerite vidžet."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvaput dodirnite i zadržite da pomjerite vidžet ili da koristite prilagođene radnje."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Širina %1$d, visina %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Vidžet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Vidžet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, širina %2$d, visina %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Dodirnite i zadržite vidžet da ga pomjerate po početnom ekranu"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Dodaj na početni ekran"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Vidžet <xliff:g id="WIDGET_NAME">%1$s</xliff:g> je dodan na početni ekran"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Prijedlozi"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Svakodnevni osnovni vidžeti"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Vijesti za vas"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Vaša zona opuštanja"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Postignite svoje ciljeve fitnesa"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Ne dajte da vas uhvati oluja"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Možda vam se svidi i ovo"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Osnovne aplikacije"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Vijesti i časopisi"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Zabava"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Društvene mreže"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Predloženo za vas"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Vidžeti aplikacije <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> su na desnoj, a pretraživanje i opcije na lijevoj strani"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# vidžet}one{# vidžet}few{# vidžeta}other{# vidžeta}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# prečica}one{# prečica}few{# prečice}other{# prečica}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Razgovori"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Pisanje bilješki"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Korisne informacije nadohvat ruke"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Da dobijete informacije bez otvaranja aplikacija, možete dodati vidžete na početni ekran"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Dodajte"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodavanje vidžeta <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Dodirnite da promijenite postavke vidžeta"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Razumijem"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Promjena postavki vidžeta"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Pretražite aplikacije"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Aplikacije se učitavaju…"</string>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Ukloni"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstaliraj"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Inform. o aplikaciji"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instaliraj u priv. pr."</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instaliraj u Privatno"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Deinstalirajte aplikaciju"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instaliraj"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne predlaži aplikaciju"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Zakači predviđanje"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instaliranje aplikacije <xliff:g id="NAME">%1$s</xliff:g>, završeno je <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se preuzima, završeno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> čeka da se instalira"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Arhivirana je aplikacija <xliff:g id="NAME">%1$s</xliff:g>. Dodirnite je da je preuzmete."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Arhivirana je aplikacija <xliff:g id="NAME">%1$s</xliff:g>. Dodirnite da je preuzmete i vratite."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Potrebno je ažurirati aplikaciju"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Aplikacija za ovu ikonu nije ažurirana. Možete je ažurirati ručno da ponovo omogućite ovu prečicu ili možete ukloniti ikonu."</string>
<string name="dialog_update" msgid="2178028071796141234">"Ažuriraj"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Ponovo pokreni"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrirajte"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Nije uspjelo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Privatan prostor"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Ostavite privatne aplikacije zaključane i sakrivene"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Privatni prostor"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Dodirnite da postavite ili otvorite"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privatno"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Postavke privatnog prostora"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Zaključavanje/otključavanje privatnog prostora"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privatno, otključano."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privatno, zaključano."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Zaključaj"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Prelazak u privatan prostor"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instaliranje aplikacija"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalirajte"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instaliranje aplikacija u privatni prostor"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Preklopni meni"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 6825237..bf578f5 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"En Mode segur, els widgets estan desactivats."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"La drecera no està disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Inici"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Defineix <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> com a aplicació d\'inici predeterminada a Configuració"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informació de l\'aplicació %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Configuració d\'ús de %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Desa la parella d\'aplicacions"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Aquesta parella d\'aplicacions no s\'admet en aquest dispositiu"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Desplega el dispositiu per utilitzar aquesta parella d\'aplicacions"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"La parella d\'aplicacions no està disponible"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Fes doble toc i mantén premut per moure un widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Fes doble toc i mantén premut per moure un widget o per utilitzar accions personalitzades."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d d\'amplada per %2$d d\'alçada"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget de <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d d\'amplada per %3$d d\'alçària"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Mantén premut el widget per moure\'l per la pantalla d\'inici"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Afegeix a la pantalla d\'inici"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"El widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> s\'ha afegit a la pantalla d\'inici"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggeriments"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Els teus essencials per al dia a dia"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Notícies per a tu"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"La teva zona de relax"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Assoleix els teus objectius de fitnes"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Que no et sorprengui el temps"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"També et pot agradar"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essencials"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Notícies i revistes"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entreteniment"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Xarxes socials"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Suggeriments per a tu"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgets de <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> a la dreta, cerca i opcions a l\'esquerra"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# drecera}other{# dreceres}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Treball"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Converses"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Presa de notes"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Informació útil a l\'abast de la mà"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Per obtenir informació sense obrir les aplicacions, pots afegir widgets a la pantalla d\'inici"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Afegeix"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Afegeix el widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Toca per canviar la configuració del widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Entesos"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Canvia la configuració del widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Cerca aplicacions"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"S\'estan carregant les aplicacions…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstal·la"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Informació de l\'aplicació"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instal·la en privat"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Desinstal·la l\'aplicació"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instal·la"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"No suggereixis l\'aplicació"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Fixa la predicció"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"S\'està instal·lant <xliff:g id="NAME">%1$s</xliff:g>; s\'ha completat un <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"S\'està baixant <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> completat"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"S\'està esperant per instal·lar <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"L\'aplicació <xliff:g id="NAME">%1$s</xliff:g> està arxivada. Toca per baixar."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"L\'aplicació <xliff:g id="NAME">%1$s</xliff:g> està arxivada. Toca per baixar-la i restaurar-la."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Cal actualitzar l\'aplicació"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"L\'aplicació d\'aquesta icona no està actualitzada. Pots actualitzar-la manualment per tornar a activar aquesta drecera o pots suprimir la icona."</string>
<string name="dialog_update" msgid="2178028071796141234">"Actualitza"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtra"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Error: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Espai privat"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Mantén les aplicacions privades bloquejades i amagades"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Toca per configurar o obrir"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privat"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Configuració d\'Espai privat"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloqueja o desbloqueja Espai privat"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privat, desbloquejat."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privat, bloquejat."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Bloqueja"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Canvia a Espai privat"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instal·la apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instal·la"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instal·la les aplicacions a Espai privat"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Menú addicional"</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index ebc7e20..9a8fc6f 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"V nouzovém režimu jsou widgety zakázány."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Zkratka není k dispozici"</string>
<string name="home_screen" msgid="5629429142036709174">"Domů"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nastavit <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> jako výchozí vstupní aplikaci v Nastavení"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdělit obrazovku"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informace o aplikaci %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavení využití pro aplikaci %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Uložit dvojici aplikací"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Tento pár aplikací není na tomto zařízení podporován"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Pokud chcete použít tento pár aplikací, rozložte zařízení"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Dvojice aplikací není k dispozici"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Widget přesunete klepnutím a podržením."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvojitým klepnutím a podržením přesunete widget, případně použijte vlastní akce."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"šířka %1$d, výška %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, šířka %2$d, výška %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Pokud chcete widgetem pohybovat po ploše, podržte ho"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Přidat na plochu"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> byl přidán na plochu"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Návrhy"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Vaše každodenní nezbytnosti"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Zprávy pro vás"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Vaše klidová zóna"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Dosažení kondičních cílů"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Mějte přehled o počasí"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Také by se vám mohlo líbit"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Nejdůležitější aplikace"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Zprávy a časopisy"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Zábava"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sociální sítě"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Návrhy pro vás"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgety <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> vpravo, vyhledávání a možnosti vlevo"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{ # widget}few{# widgety}many{# widgetu}other{# widgetů}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# zkratka}few{# zkratky}many{# zkratky}other{# zkratek}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Práce"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Konverzace"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Psaní poznámek"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Užitečné informace na dosah"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Pokud chcete mít informace k dispozici bez otevírání aplikací, můžete si na plochu přidat widgety"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Přidat"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Přidat widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Klepnutím změníte nastavení widgetu"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Rozumím"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Změnit nastavení widgetu"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Hledat v aplikacích"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Načítání aplikací…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Odinstalovat"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"O aplikaci"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instalovat soukromě"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Odinstalovat aplikaci"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Nainstalovat"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Nenavrhovat aplikaci"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Připnout předpověď"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instalace aplikace <xliff:g id="NAME">%1$s</xliff:g>, dokončeno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Stahování aplikace <xliff:g id="NAME">%1$s</xliff:g> (dokončeno <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Instalace aplikace <xliff:g id="NAME">%1$s</xliff:g> čeká na zahájení"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Aplikace <xliff:g id="NAME">%1$s</xliff:g> je archivována. Klepnutím ji stáhnete."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Aplikace <xliff:g id="NAME">%1$s</xliff:g> je archivována. Klepnutím ji můžete stáhnout a obnovit."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Je nutná aktualizace aplikace"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Aplikace pro tuto ikonu není nainstalována. Můžete ji ručně aktualizovat, aby zkratka znovu fungovala, případně můžete ikonu odstranit."</string>
<string name="dialog_update" msgid="2178028071796141234">"Aktualizovat"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtr"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Selhalo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Soukromý prostor"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Mějte soukromé aplikace uzamknuté a skryté"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Klepnutím nastavíte nebo otevřete"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Soukromé"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Nastavení soukromého prostoru"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Zamknout/odemknout soukromý prostor"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Soukromé, odemčeno."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Soukromé, uzamčeno."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Zamknout"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Převádění soukromého prostoru"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instalace aplikací"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Nainstalovat"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalovat aplikace do soukromého prostoru"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Rozbalovací nabídka"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 57053e6..208c2ef 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets er deaktiveret i Beskyttet tilstand"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Genvejen er ikke tilgængelig"</string>
<string name="home_screen" msgid="5629429142036709174">"Startskærm"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Angiv <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> som standardstartapp i Indstillinger"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Opdel skærm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinfo for %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Indstillinger for brug af %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Gem appsammenknytning"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Denne appsammenknytning understøttes ikke på enheden"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Fold enheden ud for at bruge denne appsammenknytning"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Appsammenknytning er ikke tilgængelig"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Hold en widget nede for at flytte den."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Tryk to gange, og hold en widget nede for at flytte den eller bruge tilpassede handlinger."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d i bredden og %2$d i højden"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widgetten <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>-widget, %2$d i bredden og %3$d i højden"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Hold widgetten nede for at flytte den rundt på startskærmen"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Føj til startskærm"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widgetten <xliff:g id="WIDGET_NAME">%1$s</xliff:g> blev føjet til startskærmen"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Forslag"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Dine vigtige apps"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Nyheder til dig"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Dit afslapningshjørne"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Nå dine fitnessmål"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Vær på forkant med vejret"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Du kan måske også lide"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Vigtige ting"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Aviser og blade"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Underholdning"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Socialt"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Forslag til dig"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-widgets til højre, søgning og valgmuligheder til venstre"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}one{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# genvej}one{# genvej}other{# genveje}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Arbejde"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Samtaler"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Notetagning"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Nyttige oplysninger lige ved hånden"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Hvis du vil have oplysninger uden at åbne apps, kan du føje widgets til din startskærm"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Tilføj"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Tilføj <xliff:g id="WIDGET_NAME">%1$s</xliff:g>-widget"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tryk for at ændre widgetindstillinger"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Skift widgetindstillinger"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Søg efter apps"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Indlæser apps…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Afinstaller"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Appinfo"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Installer (privat)"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Afinstaller appen"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Installer"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Foreslå ikke en app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Fastgør forslaget"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installeres. <xliff:g id="PROGRESS">%2$s</xliff:g> fuldført"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloades. <xliff:g id="PROGRESS">%2$s</xliff:g> er gennemført"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> venter på at installere"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> er arkiveret Tryk for at downloade."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> er arkiveret Tryk for at downloade og gendanne."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Appen skal opdateres"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Appen, der tilhører dette ikon, er ikke opdateret. Du kan opdatere appen manuelt for at genaktivere denne genvej, eller du kan fjerne ikonet."</string>
<string name="dialog_update" msgid="2178028071796141234">"Opdater"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Mislykket: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privat område"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Hold private apps låste og skjulte"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Tryk for at konfigurere eller åbne"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privat"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Indstillinger for privat rum"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lås/oplås det private område"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privat, oplåst."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privat, låst."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Lås"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Ændringer af tilstanden for det private område"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Installer apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Installer"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Installer apps i privat område"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overløb"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 8dcb567..380030b 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets im abgesicherten Modus deaktiviert"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Verknüpfung nicht verfügbar"</string>
<string name="home_screen" msgid="5629429142036709174">"Startbildschirm"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> in den Einstellungen als Stand-Start-App festlegen"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Splitscreen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App-Info für %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Nutzungseinstellungen für %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"App-Paar speichern"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Dieses App-Paar wird auf diesem Gerät nicht unterstützt"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Gerät aufklappen, um dieses App-Paar zu verwenden"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"Zum Verschieben des Widgets berühren und halten"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"App-Paar nicht verfügbar"</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"Zum Verschieben des Widgets gedrückt halten"</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Doppeltippen und halten, um ein Widget zu bewegen oder benutzerdefinierte Aktionen zu nutzen."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breit und %2$d hoch"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget „<xliff:g id="WIDGET_NAME">%1$s</xliff:g>“"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget „<xliff:g id="WIDGET_NAME">%1$s</xliff:g>“, %2$d breit und %3$d hoch"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Wenn du das Widget auf dem Startbildschirm verschieben möchtest, halte es gedrückt"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Zum Startbildschirm hinzufügen"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>-Widget zum Startbildschirm hinzugefügt"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Vorschläge"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Deine täglichen Essentials"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Neuigkeiten für dich"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Zum Entspannen"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Erreiche deine Fitnessziele"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Dem Wetter einen Schritt voraus"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Das könnte dir auch gefallen"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Must-haves"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Nachrichten und Zeitschriften"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Unterhaltung"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Soziale Netzwerke"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Vorschläge für dich"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-Widgets rechts, Suche und Optionen links"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# Widget}other{# Widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# Verknüpfung}other{# Verknüpfungen}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Geschäftlich"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Unterhaltungen"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Notizen"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Praktische Informationen – immer zur Hand"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Wenn du Informationen erhalten möchtest, ohne Apps zu öffnen, kannst du deinem Startbildschirm Widgets hinzufügen"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Hinzufügen"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Widget „<xliff:g id="WIDGET_NAME">%1$s</xliff:g>“ hinzufügen"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tippen, um die Widget-Einstellungen zu ändern"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Widget-Einstellungen ändern"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Apps finden"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Apps werden geladen…"</string>
@@ -72,7 +74,7 @@
<string name="label_application" msgid="8531721983832654978">"App"</string>
<string name="all_apps_label" msgid="5015784846527570951">"Alle Apps"</string>
<string name="notifications_header" msgid="1404149926117359025">"Benachrichtigungen"</string>
- <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Zum Verschieben einer Verknüpfung berühren und halten"</string>
+ <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Zum Verschieben einer Verknüpfung gedrückt halten"</string>
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Doppeltippen und halten, um eine Verknüpfung zu bewegen oder benutzerdefinierte Aktionen zu nutzen."</string>
<string name="out_of_space" msgid="6455557115204099579">"Auf diesem Startbildschirm ist kein Platz mehr vorhanden"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ablage \"Favoriten\" ist voll."</string>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Entfernen"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstallieren"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"App-Info"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Privat installieren"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Vertraul. installieren"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"App deinstallieren"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Installieren"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"App nicht vorschlagen"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Vorgeschlagene App fixieren"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> wird installiert, <xliff:g id="PROGRESS">%2$s</xliff:g> abgeschlossen"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> wird heruntergeladen, <xliff:g id="PROGRESS">%2$s</xliff:g> abgeschlossen"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Warten auf Installation von <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> ist archiviert. Zum Herunterladen tippen."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> ist archiviert. Tippe, um die App herunterzuladen und wiederherzustellen."</string>
<string name="dialog_update_title" msgid="114234265740994042">"App-Update erforderlich"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Die App für dieses Symbol wurde noch nicht aktualisiert. Du kannst sie manuell aktualisieren, um die Verknüpfung wieder zu aktivieren, oder das Symbol entfernen."</string>
<string name="dialog_update" msgid="2178028071796141234">"Aktualisieren"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Nicht mehr pausieren"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Fehler: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Privates Profil"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Hier kannst du deine privaten Apps verbergen und sperren"</string>
- <string name="ps_container_title" msgid="4391796149519594205">"Privat"</string>
- <string name="ps_container_settings" msgid="6059734123353320479">"Einstellungen für privaten Bereich"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Privaten Bereich sperren/entsperren"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Vertrauliches Profil"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Zum Einrichten oder Öffnen tippen"</string>
+ <string name="ps_container_title" msgid="4391796149519594205">"Vertraulich"</string>
+ <string name="ps_container_settings" msgid="6059734123353320479">"Einstellungen für vertrauliches Profil"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privat, entsperrt."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privat, gesperrt."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Sperren"</string>
- <string name="ps_container_transition" msgid="8667331812048014412">"Sperrzustand des privaten Bereichs wird gerade geändert"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Apps installieren"</string>
- <string name="ps_add_button_content_description" msgid="3254274107740952556">"Apps im privaten Bereich installieren"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Weitere Optionen"</string>
+ <string name="ps_container_transition" msgid="8667331812048014412">"Sperrzustand des vertraulichen Profils wird gerade geändert"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Installieren"</string>
+ <string name="ps_add_button_content_description" msgid="3254274107740952556">"Apps im vertraulichen Profil installieren"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index d868040..e86ebae 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Διαχωρισμός οθόνης"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Πληροφορίες εφαρμογής για %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Ρυθμίσεις χρήσης για %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Αποθήκευση ζεύγους εφαρμογών"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Αυτό το ζεύγος εφαρμογών δεν υποστηρίζεται σε αυτή τη συσκευή"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Το γραφικό στοιχείο <xliff:g id="WIDGET_NAME">%1$s</xliff:g> προστέθηκε στην αρχική οθόνη."</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Προτάσεις"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Τα καθημερινά απαραίτητα"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Ειδήσεις για εσάς"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Ο δικός σας τρόπος χαλάρωσης"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Επιτύχετε τους στόχους που έχετε θέσει για τη φυσική σας κατάσταση"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Ετοιμαστείτε για κάθε καιρό"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Μπορεί να σας αρέσουν επίσης"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Απαραίτητα"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Ειδήσεις και περιοδικά"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Ψυχαγωγία"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Κοινωνικά δίκτυα"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Προτεινόμενα για εσάς"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Γραφικά στοιχεία <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> στα δεξιά, αναζήτηση και επιλογές στα αριστερά"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# γραφικό στοιχείο}other{# γραφικά στοιχεία}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# συντόμευση}other{# συντομεύσεις}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Εργασίας"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Συζητήσεις"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Δημιουργία σημειώσεων"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Χρήσιμες πληροφορίες στη διάθεσή σας"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Για να λάβετε πληροφορίες χωρίς να ανοίξετε εφαρμογές, μπορείτε να προσθέσετε γραφικά στοιχεία στην αρχική οθόνη."</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Προσθήκη"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Προσθήκη του γραφικού στοιχείου <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Πατήστε για αλλαγή των ρυθμίσεων του γραφικού στοιχείου"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Το κατάλαβα"</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>
@@ -84,8 +86,9 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Απεγκατάσταση"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Πληροφ. εφαρμογής"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Εγκατ. στο απόρρητο"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Απεγκατάσταση εφαρμογής"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Εγκατάσταση"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Να μην προτείνεται η εφαρμογή"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Να μην προτείνεται"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Καρφίτσωμα πρόβλεψης"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"εγκατάσταση συντομεύσεων"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Επιτρέπει σε μια εφαρμογή την προσθήκη συντομεύσεων χωρίς την παρέμβαση του χρήστη."</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Έχει ολοκληρωθεί το <xliff:g id="PROGRESS">%2$s</xliff:g> της εγκατάστασης της εφαρμογής <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Λήψη <xliff:g id="NAME">%1$s</xliff:g>, ολοκληρώθηκε <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> σε αναμονή για εγκατάσταση"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Η εφαρμογή <xliff:g id="NAME">%1$s</xliff:g> είναι αρχειοθετημένη. Πατήστε για λήψη."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Η εφαρμογή <xliff:g id="NAME">%1$s</xliff:g> είναι αρχειοθετημένη. Πατήστε για λήψη και επαναφορά."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Απαιτείται ενημέρωση της εφαρμογής"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Η εφαρμογή για αυτό το εικονίδιο δεν έχει ενημερωθεί. Μπορείτε να την ενημερώσετε μη αυτόματα για να ενεργοποιήσετε ξανά τη συγκεκριμένη συντόμευση ή να καταργήσετε το εικονίδιο."</string>
<string name="dialog_update" msgid="2178028071796141234">"Ενημέρωση"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Φίλτρο"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Αποτυχία: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Ιδιωτικός χώρος"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Διατηρήστε τις ιδιωτικές εφαρμογές κλειδωμένες και κρυφές"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Πάτημα για ρύθμιση ή άνοιγμα"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Ιδιωτικό"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Ρυθμίσεις Ιδιωτικού χώρου"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Κλείδωμα/Ξεκλείδωμα Ιδιωτικού χώρου"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Ιδιωτικό, ξεκλειδωμένο."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Ιδιωτικό, κλειδωμένο."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Κλείδωμα"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Μετάβαση στον Ιδιωτικό χώρο"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Εγκατάσταση εφαρμογών"</string>
- <string name="ps_add_button_content_description" msgid="3254274107740952556">"Εγκατάσταση εφαρμογών στον απόρρητο χώρο"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Υπερχείλιση"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Εγκατάσταση"</string>
+ <string name="ps_add_button_content_description" msgid="3254274107740952556">"Εγκατάσταση εφαρμογών στον ιδιωτικό χώρο"</string>
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index cee4d2a..50c5976 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as the default home app in Settings"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"This app pair isn\'t supported on this device"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Unfold device to use this app pair"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"App pair isn\'t available"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Touch and hold to move a widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Double-tap & hold to move a widget or use custom actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget, %2$d wide by %3$d high"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Touch and hold the widget to move it around the home screen"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Add to home screen"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget added to home screen"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggestions"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Your daily essentials"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"News for you"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Your chill zone"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Reach your fitness goals"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Stay ahead of the weather"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"You might also like"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essentials"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"News and magazines"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entertainment"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Social"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Suggested for you"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> widgets on right, search and options on left"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# shortcut}other{# shortcuts}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Useful info at your fingertips"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"To get info without opening apps, you can add widgets to your home screen"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tap to change widget settings"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Change widget settings"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Install in private"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Uninstall app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Don\'t suggest app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Pin prediction"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installing, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> is archived. Tap to download."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> is archived. Tap to download and restore."</string>
<string name="dialog_update_title" msgid="114234265740994042">"App update required"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"The app for this icon isn\'t updated. You can update manually to re-enable this shortcut or remove the icon."</string>
<string name="dialog_update" msgid="2178028071796141234">"Update"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Private space"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Keep private apps locked and hidden"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Tap to set up or open"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Private"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Private Space Settings"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lock/Unlock Private Space"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Private, unlocked."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Private, locked."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Lock"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Private Space transitioning"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Install apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Install"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Install apps to private space"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overflow"</string>
</resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 87cd9eb..08ff6e7 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as default home app in Settings"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"This app pair isn\'t supported on this device"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Unfold device to use this app pair"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"App pair isn\'t available"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Touch and hold to move a widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Double-tap and hold to move a widget or use custom actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget, %2$d wide by %3$d high"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Touch and hold the widget to move it around the home screen"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Add to home screen"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget added to home screen"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggestions"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Your Daily Essentials"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"News For You"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Your Chill Zone"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Reach Your Fitness Goals"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Stay Ahead of the Weather"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"You Might Also Like"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essentials"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"News & magazines"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entertainment"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Social"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Suggested for you"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> widgets on right, search and options on left"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# shortcut}other{# shortcuts}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Useful info at your fingertips"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"To get info without opening apps, you can add widgets to your home screen"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tap to change widget settings"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Got it"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Change widget settings"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Install in private"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Uninstall app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Don\'t suggest app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Pin Prediction"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installing, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> is archived. Tap to download."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> is archived. Tap to download and restore."</string>
<string name="dialog_update_title" msgid="114234265740994042">"App update required"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"The app for this icon isn\'t updated. You can update manually to re-enable this shortcut, or remove the icon."</string>
<string name="dialog_update" msgid="2178028071796141234">"Update"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Private space"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Keep private apps locked and hidden"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Tap to set up or open"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Private"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Private Space Settings"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lock/Unlock Private Space"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Private, unlocked."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Private, locked."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Lock"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Private Space Transitioning"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Install apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Install"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Install apps to Private Space"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overflow"</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index cee4d2a..50c5976 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as the default home app in Settings"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"This app pair isn\'t supported on this device"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Unfold device to use this app pair"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"App pair isn\'t available"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Touch and hold to move a widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Double-tap & hold to move a widget or use custom actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget, %2$d wide by %3$d high"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Touch and hold the widget to move it around the home screen"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Add to home screen"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget added to home screen"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggestions"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Your daily essentials"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"News for you"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Your chill zone"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Reach your fitness goals"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Stay ahead of the weather"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"You might also like"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essentials"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"News and magazines"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entertainment"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Social"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Suggested for you"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> widgets on right, search and options on left"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# shortcut}other{# shortcuts}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Useful info at your fingertips"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"To get info without opening apps, you can add widgets to your home screen"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tap to change widget settings"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Change widget settings"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Install in private"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Uninstall app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Don\'t suggest app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Pin prediction"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installing, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> is archived. Tap to download."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> is archived. Tap to download and restore."</string>
<string name="dialog_update_title" msgid="114234265740994042">"App update required"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"The app for this icon isn\'t updated. You can update manually to re-enable this shortcut or remove the icon."</string>
<string name="dialog_update" msgid="2178028071796141234">"Update"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Private space"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Keep private apps locked and hidden"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Tap to set up or open"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Private"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Private Space Settings"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lock/Unlock Private Space"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Private, unlocked."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Private, locked."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Lock"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Private Space transitioning"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Install apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Install"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Install apps to private space"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overflow"</string>
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index cee4d2a..50c5976 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as the default home app in Settings"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"This app pair isn\'t supported on this device"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Unfold device to use this app pair"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"App pair isn\'t available"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Touch and hold to move a widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Double-tap & hold to move a widget or use custom actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget, %2$d wide by %3$d high"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Touch and hold the widget to move it around the home screen"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Add to home screen"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget added to home screen"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggestions"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Your daily essentials"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"News for you"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Your chill zone"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Reach your fitness goals"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Stay ahead of the weather"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"You might also like"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essentials"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"News and magazines"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entertainment"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Social"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Suggested for you"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> widgets on right, search and options on left"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# shortcut}other{# shortcuts}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Useful info at your fingertips"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"To get info without opening apps, you can add widgets to your home screen"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tap to change widget settings"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Change widget settings"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Install in private"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Uninstall app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Don\'t suggest app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Pin prediction"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installing, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> is archived. Tap to download."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> is archived. Tap to download and restore."</string>
<string name="dialog_update_title" msgid="114234265740994042">"App update required"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"The app for this icon isn\'t updated. You can update manually to re-enable this shortcut or remove the icon."</string>
<string name="dialog_update" msgid="2178028071796141234">"Update"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Private space"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Keep private apps locked and hidden"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Tap to set up or open"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Private"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Private Space Settings"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lock/Unlock Private Space"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Private, unlocked."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Private, locked."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Lock"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Private Space transitioning"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Install apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Install"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Install apps to private space"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overflow"</string>
</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index ae84841..fa6d1f1 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as default home app in Settings"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"This app pair isn\'t supported on this device"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Unfold device to use this app pair"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"App pair isn\'t available"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Touch & hold to move a widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Double-tap & hold to move a widget or use custom actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget, %2$d wide by %3$d high"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Touch & hold the widget to move it around the home screen"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Add to home screen"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget added to home screen"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggestions"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Your Daily Essentials"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"News For You"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Your Chill Zone"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Reach Your Fitness Goals"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Stay Ahead of the Weather"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"You Might Also Like"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essentials"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"News & magazines"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entertainment"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Social"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Suggested for you"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> widgets on right, search and options on left"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# shortcut}other{# shortcuts}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Useful info at your fingertips"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"To get info without opening apps, you can add widgets to your home screen"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tap to change widget settings"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Got it"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Change widget settings"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Install in private"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Uninstall app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Don\'t suggest app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Pin Prediction"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installing, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> is archived. Tap to download."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> is archived. Tap to download and restore."</string>
<string name="dialog_update_title" msgid="114234265740994042">"App update required"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"The app for this icon isn\'t updated. You can update manually to re-enable this shortcut, or remove the icon."</string>
<string name="dialog_update" msgid="2178028071796141234">"Update"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Private space"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Keep private apps locked and hidden"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Tap to set up or open"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Private"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Private Space Settings"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lock/Unlock Private Space"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Private, unlocked."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Private, locked."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Lock"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Private Space Transitioning"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Install apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Install"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Install apps to Private Space"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overflow"</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index dbb109b..5879129 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"El acceso directo no está disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Pantalla principal"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Establece <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como la app de inicio predeterminada en Configuración"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la app de %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Configuración del uso de %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Guardar vinculación"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"No se admite esta vinculación de apps en este dispositivo"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Abre el dispositivo para usar esta vinculación de apps"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"La vinculación de apps no está disponible"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Mantén presionado para mover un widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Presiona dos veces y mantén presionado para mover un widget o usar acciones personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de ancho por %2$d de alto"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d de ancho por %3$d de alto"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Mantén presionado el widget para moverlo por la pantalla principal"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Agregar a pantalla principal"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Se agregó el widget de <xliff:g id="WIDGET_NAME">%1$s</xliff:g> a la pantalla principal"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Sugerencias"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Tus esenciales diarios"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Noticias para ti"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Zona de descanso"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Logra tus objetivos de fitness"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Mantente al tanto del clima"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Puede que también te guste"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Imprescindibles"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Noticias y revistas"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entretenimiento"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Redes sociales"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sugerencias para ti"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgets de <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> a la derecha, búsqueda y opciones a la izquierda"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# acceso directo}other{# accesos directos}}"</string>
@@ -57,14 +60,13 @@
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Borra el texto del cuadro de búsqueda"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"Los widgets y accesos directos no están disponibles"</string>
<string name="no_search_results" msgid="3787956167293097509">"No se encontraron widgets ni accesos directos"</string>
- <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Personales"</string>
+ <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Personal"</string>
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabajo"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversaciones"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Tomar notas"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Información útil a tu alcance"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Para recibir información de apps sin abrirlas, puedes agregar widgets a la pantalla principal"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Agregar"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Agregar widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Presiona para cambiar la configuración del widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Entendido"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Cambiar la configuración del widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Buscar apps"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Cargando apps…"</string>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Quitar"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Información de app"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instala en privado"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instalar en privado"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Desinstalar app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instalar"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"No sugerir app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Fijar predicción"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Se está instalando <xliff:g id="NAME">%1$s</xliff:g>; <xliff:g id="PROGRESS">%2$s</xliff:g> completado"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Se completó el <xliff:g id="PROGRESS">%2$s</xliff:g> de la descarga de <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Instalación de <xliff:g id="NAME">%1$s</xliff:g> en espera"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> está archivada. Presiona para descargar."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> está archivada. Presiona para descargar y restablecer."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Es necesario actualizar la app"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"No se actualizó la app de este ícono. Puedes actualizarla manualmente para rehabilitar el acceso directo, o bien quitar el ícono."</string>
<string name="dialog_update" msgid="2178028071796141234">"Actualizar"</string>
@@ -167,8 +170,8 @@
<string name="action_deep_shortcut" msgid="2864038805849372848">"Accesos directos"</string>
<string name="action_dismiss_notification" msgid="5909461085055959187">"Descartar"</string>
<string name="accessibility_close" msgid="2277148124685870734">"Cerrar"</string>
- <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personales"</string>
- <string name="all_apps_work_tab" msgid="4884822796154055118">"De trabajo"</string>
+ <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
+ <string name="all_apps_work_tab" msgid="4884822796154055118">"Trabajo"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabajo"</string>
<string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Las apps de trabajo tienen una insignia y el administrador de TI las puede ver"</string>
<string name="work_profile_edu_accept" msgid="6069788082535149071">"Entendido"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtro"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Error: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Espacio privado"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Mantén las apps privadas bloqueadas y ocultas"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Presiona para configurar o abrir"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privado"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Configuración de Espacio privado"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloquear o desbloquear Espacio privado"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privado (desbloqueado)"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privado (bloqueado)"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Bloqueo"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Pasar a Espacio privado"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instala apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalar"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instala las apps en el espacio privado"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Ampliada"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 176bfe3..8d384ac 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo Seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Acceso directo no disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Define <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como aplicación de inicio predeterminada en Ajustes"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la aplicación %1$s"</string>
- <string name="save_app_pair" msgid="5647523853662686243">"Guardar aplicaciones emparejadas"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Ajustes de uso para %1$s"</string>
+ <string name="save_app_pair" msgid="5647523853662686243">"Guardar apps emparejadas"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"El dispositivo no admite esta aplicación emparejada"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Despliega el dispositivo para usar esta aplicación emparejada"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"La aplicación emparejada no está disponible"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Mantén pulsado un widget para moverlo"</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toca dos veces y mantén pulsado un widget para moverlo o usar acciones personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de ancho por %2$d de alto"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget de <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d de ancho por %3$d de alto"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Mantén pulsado el widget para moverlo por la pantalla de inicio"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Añadir a pantalla de inicio"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> añadido a la pantalla de inicio"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Sugerencias"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Lo esencial para el día a día"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Noticias para ti"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Tu zona de descanso"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Logra tus objetivos de actividad física"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Infórmate sobre el tiempo"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"También te puede interesar"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Imprescindibles"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Noticias y revistas"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entretenimiento"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Redes sociales"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sugerencias para ti"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgets de <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> a la derecha, búsqueda y opciones a la izquierda"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# acceso directo}other{# accesos directos}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabajo"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversaciones"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Toma de notas"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Información útil al alcance de la mano"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Para ver información sin abrir una aplicación, puedes añadir widgets a la pantalla de inicio"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Añadir"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Añadir widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Toca para cambiar los ajustes del widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Entendido"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Cambiar ajustes del widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Buscar aplicaciones"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Cargando aplicaciones…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Información de la aplicación"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Descargar en privado"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Desinstalar aplicación"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instalar"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"No sugerir aplicación"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Fijar predicción"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instalando <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> completado"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Descargando <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="PROGRESS">%2$s</xliff:g> completado)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Esperando para instalar <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> está archivada. Toca para descargarla."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> está archivada. Toca para descargar y restaurar."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Debes actualizar la aplicación"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"La aplicación de este icono no está actualizada. Puedes actualizarla manualmente para volver a habilitar este acceso directo o puedes eliminar el icono."</string>
<string name="dialog_update" msgid="2178028071796141234">"Actualizar"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtro"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Se ha producido un error: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Espacio privado"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Bloquea y oculta tus aplicaciones privadas"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Toca para configurarlo o abrirlo"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privado"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Ajustes del espacio privado"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloquear/Desbloquear espacio privado"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privado, desbloqueado."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privado, bloqueado."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Bloquear"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Cambiar a espacio privado"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Descargar aplicaciones"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalar"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Descargar aplicaciones en el espacio privado"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Desplegable"</string>
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 830abf0..44448a6 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Turvarežiimis on vidinad keelatud"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Otsetee pole saadaval"</string>
<string name="home_screen" msgid="5629429142036709174">"Avakuva"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Määrake rakendus <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> seadetes avakuva vaikerakenduseks"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jagatud ekraanikuva"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Rakenduse teave: %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Kasutuse seaded: %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Salvesta rakendusepaar"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"See rakendusepaar ei ole selles seadmes toetatud"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Selle rakendusepaari kasutamiseks voltige seade lahti"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Rakendusepaar ei ole saadaval"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Vidina teisaldamiseks puudutage ja hoidke all."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Vidina teisaldamiseks või kohandatud toimingute kasutamiseks topeltpuudutage ja hoidke all."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<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_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>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Kasulikud vidinad"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Uudised teile"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Teie lõõgastumiskoht"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Saavutage oma treeningueesmärgid"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Olge ilmateatega kursis"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Teile võivad meeldida ka need"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Põhiasjad"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Uudised ja ajakirjad"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Meelelahutus"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Suhtlus"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Teile soovitatud"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Teenuse <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> vidinad paremal, otsing ja valikud vasakul"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# vidin}other{# vidinat}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# otsetee}other{# otseteed}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Töö"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Vestlused"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Märkmete tegemine"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Kasulik teave on teie käeulatuses"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Teabe saamiseks rakendusi avamata võite oma avakuvale lisada vidinaid"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Lisa"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Lisa vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Puudutage vidina seadete muutmiseks"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Selge"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Vidina seadete muutmine"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Otsige rakendusi"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Rakenduste laadimine …"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalli"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Rakenduse teave"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Privaatselt installimine"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Desinstalli rakendus"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Installimine"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Ära soovita rakendust"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Kinnita ennustus"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Üksust <xliff:g id="NAME">%1$s</xliff:g> installitakse, <xliff:g id="PROGRESS">%2$s</xliff:g> on valmis"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Rakenduse <xliff:g id="NAME">%1$s</xliff:g> allalaadimine, <xliff:g id="PROGRESS">%2$s</xliff:g> on valmis"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> on installimise ootel"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> on arhiivitud. Puudutage allalaadimiseks."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> on arhiivitud. Puudutage allalaadimiseks ja taastamiseks."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Rakendust tuleb värskendada"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Selle ikooni rakendust pole värskendatud. Otsetee uuesti lubamiseks võite rakendust käsitsi värskendada või ikooni eemaldada."</string>
<string name="dialog_update" msgid="2178028071796141234">"Värskenda"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Nurjus: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privaatne ruum"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Hoidke privaatsed rakendused lukustatud ja peidetuna"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Seadistamiseks või avamiseks puudutage"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privaatne"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Privaatse ruumi seaded"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Privaatse ruumi lukustamine/avamine"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privaatne, võrgulukuta."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privaatne, lukustatud."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Lukk"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Privaatse ruumi üleviimine"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Rakenduste installimine"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Installi"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Rakenduste installimine privaatses ruumis"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Ületäide"</string>
</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 047ef0a..6fc4cf4 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -26,28 +26,31 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Deskargatutako aplikazioa modu seguruan desgaitu da"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgetak desgaitu egin dira modu seguruan"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lasterbideak ez daude erabilgarri"</string>
- <string name="home_screen" msgid="5629429142036709174">"Hasierako pantaila"</string>
+ <string name="home_screen" msgid="5629429142036709174">"Orri nagusia"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ezarri <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> hasierako aplikazio lehenetsi gisa ezarpenetan"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantaila zatitzea"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s aplikazioari buruzko informazioa"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s aplikazioaren erabilera-ezarpenak"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Gorde aplikazio parea"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Aplikazio pare hori ez da onartzen gailu honetan"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Zabaldu gailua aplikazio pare hau erabiltzeko"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Aplikazio parea ez dago erabilgarri"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Eduki sakatuta widget bat mugitzeko."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Sakatu birritan eta eduki sakatuta widget bat mugitzeko edo ekintza pertsonalizatuak erabiltzeko."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d zabal eta %2$d luze"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widgeta"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widgeta, %2$d zabal eta %3$d altu"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Widgeta orri nagusian zehar mugitzeko, eduki ezazu sakatuta"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Gehitu orri nagusian"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widgeta orri nagusian gehitu da"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Iradokizunak"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Eguneroko funtsezkoak"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Zuretzako albisteak"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Lasaitzeko gunea"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Erdietsi zure fitness-helburuak"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Hartu aurrea eguraldiari"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Gustatuko zaizkizulakoan"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Oinarrizkoak"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Albisteak eta aldizkariak"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Aisia"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sare sozialak"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Zuri iradokiak"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> zerbitzuaren widgetak eskuinean, bilaketa eta aukerak ezkerrean"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widget}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# lasterbide}other{# lasterbide}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Lanekoak"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Elkarrizketak"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Oharrak idazteko"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Informazio erabilgarria beti eskura"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Aplikaziorik ireki beharrik gabe informazioa zuzenean jasotzeko, gehitu widgetak orri nagusian"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Gehitu"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Gehitu <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widgeta"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Sakatu hau widgeten ezarpenak aldatzeko"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Ados"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Aldatu widgeten ezarpenak"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Bilatu aplikazioetan"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Aplikazioak kargatzen…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalatu"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Aplikazioaren informazioa"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instalatu pribatuan"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Desinstalatu aplikazioa"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instalatu"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Ez iradoki aplikazioa"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Ainguratu iragarpena"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> instalatzen, <xliff:g id="PROGRESS">%2$s</xliff:g> osatuta"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> deskargatzen, <xliff:g id="PROGRESS">%2$s</xliff:g> osatuta"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> instalatzeko zain"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> artxibatuta dago. Deskargatzeko, sakatu hau."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> artxibatuta dago. Sakatu deskargatzeko eta leheneratzeko."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Aplikazioa eguneratu egin behar da"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Ikonoaren aplikazioa ez dago eguneratuta. Lasterbidea berriro gaitzeko, eskuz egunera dezakezu aplikazioa. Bestela, kendu ikonoa."</string>
<string name="dialog_update" msgid="2178028071796141234">"Eguneratu"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Iragazi"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Huts egin du: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Eremu pribatua"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Mantendu aplikazio pribatuak blokeatuta eta ezkutatuta"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Sakatu konfiguratzeko edo irekitzeko"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Pribatua"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Eremu pribatuaren ezarpenak"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Blokeatu/Desblokeatu eremu pribatua"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Pribatua, desblokeatuta."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Pribatua, blokeatuta."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Blokeatu"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Eremu pribaturako trantsizioa"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Aplikazioak instalatu"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalatu"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalatu aplikazioak eremu pribatuan"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Luzapena"</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index feaf724..b9d55be 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"صفحهٔ دونیمه"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"اطلاعات برنامه %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"تنظیمات مصرف برای %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ذخیره جفت برنامه"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"از این جفت برنامه در این دستگاه پشتیبانی نمیشود"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"برای استفاده از این جفت برنامه، دستگاه را باز کنید"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"جفت برنامه دردسترس نیست"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"برای جابهجا کردن ابزارک، لمس کنید و نگه دارید."</string>
- <string name="long_accessible_way_to_add" msgid="2733588281439571974">"برای جابهجا کردن ابزارک یا استفاده از کنشهای سفارشی، دوضربه بزنید و نگه دارید."</string>
+ <string name="long_accessible_way_to_add" msgid="2733588281439571974">"برای جابهجا کردن ابزارک یا استفاده از کنشهای سفارشی، دو تکضرب بزنید و نگه دارید."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d عرض در %2$d طول"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"ابزارک <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"ابزارک <xliff:g id="WIDGET_NAME">%1$s</xliff:g> به صفحه اصلی اضافه شد"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"پیشنهادها"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"ملزومات روزانه"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"اخبار برای شما"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"منطقه آرامش شما"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"دستیابی به اهداف تناسب اندام"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"آبوهوا را پیشبینی کنید"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"شاید این را هم بپسندید"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ضروریات"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"اخبار و مجله"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"سرگرمی"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"اجتماعی"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"پیشنهاداتی برای شما"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"ابزارکهای <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> در سمت چپ، جستجو و گزینهها در سمت راست"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# ابزارک}one{# ابزارک}other{# ابزارک}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# میانبر}one{# میانبر}other{# میانبر}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"کار"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"مکالمهها"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"یادداشتبرداری"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"دسترسی آسان به اطلاعات سودمند"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"با افزودن ابزارکها به صفحه اصلی میتوانید اطلاعات را بدون باز کردن برنامهها دریافت کنید"</string>
- <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"برای تغییر تنظیمات ابزارک، ضربه بزنید"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"متوجهام"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"افزودن"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"افزودن ابزارک <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</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>
@@ -73,7 +75,7 @@
<string name="all_apps_label" msgid="5015784846527570951">"همه برنامهها"</string>
<string name="notifications_header" msgid="1404149926117359025">"اعلانها"</string>
<string name="long_press_shortcut_to_add" msgid="5405328730817637737">"برای جابهجا کردن میانبر، لمس کنید و نگه دارید."</string>
- <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"برای جابهجا کردن میانبر یا استفاده از کنشهای سفارشی، دوضربه بزنید و نگه دارید."</string>
+ <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"برای جابهجا کردن میانبر یا استفاده از کنشهای سفارشی، دو تکضرب بزنید و نگه دارید."</string>
<string name="out_of_space" msgid="6455557115204099579">"فضای خالی در این صفحه اصلی وجود ندارد"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"فضای بیشتری در سینی موارد دلخواه وجود ندارد"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"فهرست برنامهها"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"حذف نصب"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"اطلاعات برنامه"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"نصب در نمایه خصوصی"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"حذف نصب برنامه"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"نصب"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"برنامه پیشنهاد داده نشود"</string>
<string name="pin_prediction" msgid="4196423321649756498">"سنجاق کردن پیشنهاد"</string>
@@ -95,7 +98,7 @@
<string name="permdesc_write_settings" msgid="726859348127868466">"به برنامه اجازه میدهد تنظیمات و میانبرهای صفحه اصلی را تغییر دهد."</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="gadget_complete_setup_text" msgid="309040266978007925">"برای تکمیل راهاندازی تکضرب بزنید"</string>
<string name="uninstall_system_app_text" msgid="4172046090762920660">"این برنامه سیستمی است و حذف نصب نمیشود."</string>
<string name="folder_hint_text" msgid="5174843001373488816">"ویرایش نام"</string>
<string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> غیرفعال شد"</string>
@@ -104,8 +107,8 @@
<string name="workspace_scroll_format" msgid="8458889198184077399">"صفحه اصلی %1$d از %2$d"</string>
<string name="workspace_new_page" msgid="257366611030256142">"صفحه اصلی جدید"</string>
<string name="folder_opened" msgid="94695026776264709">"پوشه باز شده، <xliff:g id="WIDTH">%1$d</xliff:g> در <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
- <string name="folder_tap_to_close" msgid="4625795376335528256">"برای بستن پوشه، ضربه بزنید"</string>
- <string name="folder_tap_to_rename" msgid="4017685068016979677">"برای ذخیره تغییر نام، ضربه بزنید"</string>
+ <string name="folder_tap_to_close" msgid="4625795376335528256">"برای بستن پوشه، تکضرب بزنید"</string>
+ <string name="folder_tap_to_rename" msgid="4017685068016979677">"برای ذخیره تغییر نام، تکضرب بزنید"</string>
<string name="folder_closed" msgid="4100806530910930934">"پوشه بسته شد"</string>
<string name="folder_renamed" msgid="1794088362165669656">"نام پوشه به <xliff:g id="NAME">%1$s</xliff:g> تغییر کرد"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"پوشه: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> مورد"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> درحال نصب است، <xliff:g id="PROGRESS">%2$s</xliff:g> تکمیل شده است"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"درحال بارگیری <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="PROGRESS">%2$s</xliff:g> کامل شد"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> درانتظار نصب"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> بایگانی شده است. برای بارگیری ضربه بزنید."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> بایگانی شده است. برای بارگیری و بازیابی تکضرب بزنید."</string>
<string name="dialog_update_title" msgid="114234265740994042">"برنامه باید بهروز شود"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"برنامه برای این نماد بهروز نشده است. میتوانید آن را بهصورت دستی بهروز کنید تا میانبر دوباره فعال شود، یا نماد را بردارید."</string>
<string name="dialog_update" msgid="2178028071796141234">"بهروزرسانی"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"فیلتر"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"ناموفق بود: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"فضای خصوصی"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"برنامههای خصوصی قفل و پنهان نگه داشته میشود"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"برای راهاندازی یا باز کردن، تکضرب بزنید"</string>
<string name="ps_container_title" msgid="4391796149519594205">"خصوصی"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"تنظیمات «فضای خصوصی»"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"قفل/ باز کردن «فضای خصوصی»"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"خصوصی، باز."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"خصوصی، قفل."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"قفل کردن"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"انتقال «فضای خصوصی»"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"نصب برنامهها"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"نصب"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"نصب برنامهها در «فضای خصوصی»"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"سرریز"</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 3198a03..310053e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgetit poistettu käytöstä vikasietotilassa"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pikakuvake ei ole käytettävissä."</string>
<string name="home_screen" msgid="5629429142036709174">"Etusivu"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Valitse <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> oletusaloitusnäyttösovellukseksi asetuksissa"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jaettu näyttö"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Sovellustiedot: %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Käyttöasetus tälle: %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Tallenna sovelluspari"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Sovellusparia ei tueta tällä laitteella"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Avaa taitettu laite, niin voit käyttää sovellusparia"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Sovelluspari ei ole saatavilla"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Kosketa pitkään, niin voit siirtää widgetiä."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Kaksoisnapauta ja paina pitkään, niin voit siirtää widgetiä tai käyttää muokattuja toimintoja."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Leveys: %1$d, korkeus: %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget: <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, leveys %2$d ja korkeus %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Voit siirtää widgetiä aloitusnäytöllä koskettamalla sitä pitkään"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Lisää aloitusnäytölle"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget lisätty aloitusnäytölle: <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Ehdotukset"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Tärkeät asiat päivään"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Uutisia sinulle"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Ota rennosti"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Saavuta kuntoilutavoitteet"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Pysy ajan tasalla säästä"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Saatat pitää myös näistä"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Kaikki tarvittava"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Uutiset ja aikakauslehdet"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Viihde"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Some"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sinulle ehdotetut"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> widgetit oikealla, haku ja vaihtoehdot vasemmalla"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgetiä}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# pikakuvake}other{# pikakuvaketta}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Työ"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Keskustelut"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Muistiinpanojen tekeminen"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Hyödyllisiä tietoja käden ulottuvilla"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Jos haluat nähdä tietoja avaamatta sovelluksia, voit lisätä aloitusnäytölle widgetejä"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Lisää"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Lisää widget: <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Napauta, niin voit muuttaa widgetin asetuksia"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Muuta widgetin asetuksia"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Hae sovelluksia"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Ladataan sovelluksia…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Poista asennus"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Sovelluksen tiedot"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Asenna yksityisesti"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Poista sovellus"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Asenna"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Älä ehdota sovellusta"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Kiinnitä sovellus"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> asennetaan, <xliff:g id="PROGRESS">%2$s</xliff:g> valmis"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> latautuu, valmiina <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> odottaa asennusta"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> on arkistoitu. Lataa napauttamalla."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> on arkistoitu. Lataa ja palauta napauttamalla."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Sovelluspäivitys vaaditaan"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Kuvakkeen sovellusta ei ole päivitetty. Voit ottaa pikakuvakkeen uudelleen käyttöön päivittämällä sovelluksen tai poistaa kuvakkeen."</string>
<string name="dialog_update" msgid="2178028071796141234">"Päivitä"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Suodatin"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Epäonnistui: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Yksityinen tila"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Pidä yksityiset sovellukset lukittuna ja piilossa"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Ota käyttöön tai avaa napauttamalla"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Yksityinen"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Yksityisen tilan asetukset"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lukitse yksityinen tila / avaa sen lukitus"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Yksityinen, lukitsematon."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Yksityinen, lukittu."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Lukko"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Yksityisen tilan siirtäminen"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Asenna sovelluksia"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Asenna"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Asenna sovelluksia yksityiseen tilaan"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Ylivuoto"</string>
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 4d7c443..11b2c01 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -21,33 +21,36 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Lanceur3"</string>
<string name="work_folder_name" msgid="3753320833950115786">"Travail"</string>
- <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
- <string name="activity_not_available" msgid="7456344436509528827">"Application indisponible"</string>
- <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sans échec."</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"L\'appli n\'est pas installée."</string>
+ <string name="activity_not_available" msgid="7456344436509528827">"Appli indisponible"</string>
+ <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'appli téléchargée est désactivée en mode sans échec."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets désactivés en mode sans échec"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Le raccourci n\'est pas disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Définissez <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> comme appli d\'accueil par défaut dans les paramètres"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran divisé"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Renseignements sur l\'appli pour %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Enr. paire d\'applis"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
- <string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Cette paire d\'applications n\'est pas prise en charge sur cet appareil"</string>
- <string name="app_pair_needs_unfold" msgid="4588897528143807002">"Déplier l\'appareil pour utiliser cette paire d\'applications"</string>
+ <string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Cette paire d\'applis n\'est pas prise en charge sur cet appareil"</string>
+ <string name="app_pair_needs_unfold" msgid="4588897528143807002">"Déplier l\'appareil pour utiliser cette paire d\'applis"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"La Paire d\'applis n\'est pas offerte"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Maintenez le doigt sur un widget pour le déplacer."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Touchez 2x un widget et maintenez le doigt dessus pour le déplacer ou utiliser des actions personnalisées."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largeur sur %2$d de hauteur"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d de largeur sur %3$d de hauteur"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Maintenez le doigt sur le widget pour le déplacer sur l\'écran d\'accueil"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Ajouter à l\'écran d\'accueil"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Le widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> a été ajouté à l\'écran d\'accueil"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggestions"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Vos indispensables au quotidien"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Actualités personnalisées"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Zone de divertissement"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Objectifs de mise en forme"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"À l\'affût de la météo"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Autres recommandations"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essentiels"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Actualités et magazines"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Divertissement"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Médias sociaux"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Suggestions personnalisées"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgets <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> à droite, recherche et options à gauche"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}one{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# raccourci}one{# raccourci}other{# raccourcis}}"</string>
@@ -61,44 +64,44 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Professionnels"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Prise de note"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Renseignements utiles à portée de main"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Pour obtenir des informations sans ouvrir d\'applications, vous pouvez ajouter des widgets à votre écran d\'accueil"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Ajouter"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Ajoutez le widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Touchez pour modifier les paramètres du widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Modifier les paramètres du widget"</string>
- <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Rechercher dans les applications"</string>
- <string name="all_apps_loading_message" msgid="5813968043155271636">"Chargement des applications en cours…"</string>
- <string name="all_apps_no_search_results" msgid="3200346862396363786">"Aucune application trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
- <string name="label_application" msgid="8531721983832654978">"Application"</string>
- <string name="all_apps_label" msgid="5015784846527570951">"Toutes les applications"</string>
+ <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Rechercher dans les applis"</string>
+ <string name="all_apps_loading_message" msgid="5813968043155271636">"Chargement des applis en cours…"</string>
+ <string name="all_apps_no_search_results" msgid="3200346862396363786">"Aucune appli trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
+ <string name="label_application" msgid="8531721983832654978">"Appli"</string>
+ <string name="all_apps_label" msgid="5015784846527570951">"Toutes les applis"</string>
<string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
<string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Maintenez le doigt sur un raccourci pour le déplacer."</string>
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Touchez deux fois un raccourci et maintenez le doigt dessus pour le déplacer ou utiliser des actions personnalisées."</string>
<string name="out_of_space" msgid="6455557115204099579">"Pas d\'espace libre sur cet écran d\'accueil"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Il n\'y a plus d\'espace dans la zone des favoris"</string>
- <string name="all_apps_button_label" msgid="8130441508702294465">"Liste des applications"</string>
+ <string name="all_apps_button_label" msgid="8130441508702294465">"Liste des applis"</string>
<string name="all_apps_search_results" msgid="5889367432531296759">"Résultats de recherche"</string>
- <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Liste des applications personnelles"</string>
- <string name="all_apps_button_work_label" msgid="7270707118948892488">"Liste des applications professionnelles"</string>
+ <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Liste des applis personnelles"</string>
+ <string name="all_apps_button_work_label" msgid="7270707118948892488">"Liste des applis professionnelles"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"Supprimer"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Désinstaller"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Détails de l\'appli"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Installer dans privé"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Désinstaller l\'appli"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Installer"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne pas suggérer d\'application"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne pas suggérer d\'appli"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Épingler la prédiction"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"installer des raccourcis"</string>
- <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet à une appli d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."</string>
<string name="permlab_read_settings" msgid="5136500343007704955">"lire les paramètres et les raccourcis de la page d\'accueil"</string>
- <string name="permdesc_read_settings" msgid="4208061150510996676">"Permet à l\'application de lire les paramètres et les raccourcis de l\'écran d\'accueil."</string>
+ <string name="permdesc_read_settings" msgid="4208061150510996676">"Permet à l\'appli de lire les paramètres et les raccourcis de l\'écran d\'accueil."</string>
<string name="permlab_write_settings" msgid="4820028712156303762">"modifier les paramètres et les raccourcis de la page d\'accueil"</string>
- <string name="permdesc_write_settings" msgid="726859348127868466">"Permet à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil."</string>
+ <string name="permdesc_write_settings" msgid="726859348127868466">"Permet à l\'appli de modifier les paramètres et les raccourcis de l\'écran d\'accueil."</string>
<string name="gadget_error_text" msgid="740356548025791839">"Impossible de charger le widget"</string>
<string name="gadget_setup_text" msgid="8348374825537681407">"Paramètres du widget"</string>
<string name="gadget_complete_setup_text" msgid="309040266978007925">"Touchez pour terminer la configuration"</string>
- <string name="uninstall_system_app_text" msgid="4172046090762920660">"Impossible de désinstaller cette application, car il s\'agit d\'une application système."</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Impossible de désinstaller cette appli, car il s\'agit d\'une appli système."</string>
<string name="folder_hint_text" msgid="5174843001373488816">"Modifier le nom"</string>
- <string name="disabled_app_label" msgid="6673129024321402780">"L\'application <xliff:g id="APP_NAME">%1$s</xliff:g> est désactivée"</string>
+ <string name="disabled_app_label" msgid="6673129024321402780">"L\'appli <xliff:g id="APP_NAME">%1$s</xliff:g> est désactivée"</string>
<string name="dotted_app_label" msgid="1865617679843363410">"{count,plural, =1{{app_name} a # notification}one{{app_name} a # notification}other{{app_name} a # notifications}}"</string>
<string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d sur %2$d"</string>
<string name="workspace_scroll_format" msgid="8458889198184077399">"Écran d\'accueil %1$d sur %2$d"</string>
@@ -110,7 +113,7 @@
<string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> élément(s)"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> éléments ou plus"</string>
- <string name="app_pair_name_format" msgid="8134106404716224054">"Paire d\'applications : <xliff:g id="APP1">%1$s</xliff:g> et <xliff:g id="APP2">%2$s</xliff:g>"</string>
+ <string name="app_pair_name_format" msgid="8134106404716224054">"Paire d\'applis : <xliff:g id="APP1">%1$s</xliff:g> et <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fond d\'écran et style"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Modifier l\'écran d\'accueil"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Paramètres d\'accueil"</string>
@@ -121,23 +124,23 @@
<string name="notification_dots_desc_on" msgid="1679848116452218908">"Activé"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Désactivé"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"L\'accès aux notifications est requis"</string>
- <string name="msg_missing_notification_access" msgid="281113995110910548">"Pour afficher les points de notification, activez les notifications d\'application pour <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="msg_missing_notification_access" msgid="281113995110910548">"Pour afficher les points de notification, activez les notifications d\'appli pour <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="title_change_settings" msgid="1376365968844349552">"Modifier les paramètres"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Afficher les points de notification"</string>
<string name="developer_options_title" msgid="700788437593726194">"Options pour les développeurs"</string>
- <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Ajouter les icônes des applications à l\'écran d\'accueil"</string>
- <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pour les nouvelles applications"</string>
+ <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Ajouter les icônes des applis à l\'écran d\'accueil"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pour les nouvelles applis"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"Inconnu"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Supprimer"</string>
<string name="abandoned_search" msgid="891119232568284442">"Rechercher"</string>
- <string name="abandoned_promises_title" msgid="7096178467971716750">"Cette application n\'est pas installée"</string>
- <string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'application liée à cette icône n\'est pas installée. Vous pouvez la supprimer ou rechercher l\'application et l\'installer manuellement."</string>
- <string name="app_installing_title" msgid="5864044122733792085">"Installation de l\'application <xliff:g id="NAME">%1$s</xliff:g> en cours, <xliff:g id="PROGRESS">%2$s</xliff:g> terminée"</string>
+ <string name="abandoned_promises_title" msgid="7096178467971716750">"Cette appli n\'est pas installée"</string>
+ <string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'appli liée à cette icône n\'est pas installée. Vous pouvez la supprimer ou rechercher l\'appli et l\'installer manuellement."</string>
+ <string name="app_installing_title" msgid="5864044122733792085">"Installation de l\'appli <xliff:g id="NAME">%1$s</xliff:g> en cours, <xliff:g id="PROGRESS">%2$s</xliff:g> terminée"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Téléchargement de <xliff:g id="NAME">%1$s</xliff:g> : <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> en attente d\'installation"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"L\'application <xliff:g id="NAME">%1$s</xliff:g> est archivée. Toucher pour télécharger."</string>
- <string name="dialog_update_title" msgid="114234265740994042">"Mise à jour de l\'application requise"</string>
- <string name="dialog_update_message" msgid="4176784553982226114">"L\'application pour cette icône n\'est pas à jour. Vous pouvez soit la mettre à jour manuellement pour réactiver ce raccourci, soit retirer l\'icône."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"L\'appli <xliff:g id="NAME">%1$s</xliff:g> est archivée. Touchez le bouton pour télécharger et restaurer l\'appli."</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Mise à jour de l\'appli requise"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"L\'appli pour cette icône n\'est pas à jour. Vous pouvez soit la mettre à jour manuellement pour réactiver ce raccourci, soit retirer l\'icône."</string>
<string name="dialog_update" msgid="2178028071796141234">"Mettre à jour"</string>
<string name="dialog_remove" msgid="6510806469849709407">"Retirer"</string>
<string name="widgets_list" msgid="796804551140113767">"Liste des widgets"</string>
@@ -170,26 +173,26 @@
<string name="all_apps_personal_tab" msgid="4190252696685155002">"Personnel"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"Travail"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil professionnel"</string>
- <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Les applications professionnelles sont indiquées par un badge et elles sont visibles pour votre administrateur informatique"</string>
+ <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Les applis professionnelles sont indiquées par un badge et elles sont visibles pour votre administrateur informatique"</string>
<string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
- <string name="work_apps_paused_title" msgid="3040901117349444598">"Les applications professionnelles sont interrompues"</string>
- <string name="work_apps_paused_info_body" msgid="1687828929959237477">"Vous ne recevrez pas de notifications de vos applications professionnelles"</string>
- <string name="work_apps_paused_body" msgid="261634750995824906">"Les applications professionnelles ne peuvent ni vous envoyer de notifications, ni utiliser la pile, ni accéder à votre position"</string>
- <string name="work_apps_paused_telephony_unavailable_body" msgid="8358872357502756790">"Vous ne recevrez pas d\'appels téléphoniques, de messages texte ni de notifications de vos applications professionnelles"</string>
- <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"Les applications professionnelles sont indiquées par un badge et sont visibles pour votre administrateur informatique"</string>
+ <string name="work_apps_paused_title" msgid="3040901117349444598">"Les applis professionnelles sont interrompues"</string>
+ <string name="work_apps_paused_info_body" msgid="1687828929959237477">"Vous ne recevrez pas de notifications de vos applis professionnelles"</string>
+ <string name="work_apps_paused_body" msgid="261634750995824906">"Les applis professionnelles ne peuvent ni vous envoyer de notifications, ni utiliser la pile, ni accéder à votre position"</string>
+ <string name="work_apps_paused_telephony_unavailable_body" msgid="8358872357502756790">"Vous ne recevrez pas d\'appels téléphoniques, de messages texte ni de notifications de vos applis professionnelles"</string>
+ <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"Les applis professionnelles sont indiquées par un badge et sont visibles pour votre administrateur informatique"</string>
<string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"OK"</string>
- <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Mettre en pause les applications professionnelles"</string>
+ <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Mettre en pause les applis professionnelles"</string>
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Réactiver"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrer"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Échec : <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Espace privé"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Verrouiller et cacher les applications privées"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Touchez pour configurer ou ouvrir"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privé"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Paramètres de l\'Espace privé"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Verrouiller/Déverrouiller l\'Espace privé"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privé, déverrouillé."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privé, verrouillé."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Verrouiller"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Transition vers l\'Espace privé"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Installer des applications"</string>
- <string name="ps_add_button_content_description" msgid="3254274107740952556">"Installer des applications dans l\'Espace privé"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Menu à développer"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Installer"</string>
+ <string name="ps_add_button_content_description" msgid="3254274107740952556">"Installer des applis dans l\'Espace privé"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index f680305..b4c5ec6 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Les widgets sont désactivés en mode sécurisé."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Raccourci non disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Définissez <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> comme application d\'accueil par défaut dans Paramètres"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran partagé"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Infos sur l\'appli pour %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Enregistrer la paire d\'applis"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Cette paire d\'applications n\'est pas prise en charge sur cet appareil"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Dépliez l\'appareil pour utiliser cette paire d\'applications"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"La paire d\'applications n\'est pas disponible"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Appuyez de manière prolongée sur un widget pour le déplacer."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Appuyez deux fois et maintenez la pression pour déplacer widget ou utiliser actions personnalisées."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largeur et %2$d de hauteur"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d de large par %3$d de haut"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Appuyez de manière prolongée sur le widget pour le déplacer sur l\'écran d\'accueil"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Ajouter à l\'écran d\'accueil"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> ajouté à l\'écran d\'accueil"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggestions"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Vos indispensables du jour"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Actualités personnalisées"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Votre espace détente"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Atteignez vos objectifs forme"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Soyez au fait de la météo"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Découvrez également"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Indispensables"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Actualités et magazines"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Divertissement"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Réseaux sociaux"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Recommandations"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgets <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> à droite, recherche et options à gauche"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}one{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# raccourci}one{# raccourci}other{# raccourcis}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Professionnels"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Prise de notes"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Infos utiles à portée de main"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Pour obtenir des infos sans ouvrir d\'applis, vous pouvez ajouter des widgets à votre écran d\'accueil"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Ajouter"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Ajoutez un widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Appuyez pour modifier les paramètres du widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Modifier les paramètres du widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Rechercher dans les applications"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Chargement des applications…"</string>
@@ -84,8 +86,9 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Désinstaller"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Infos sur l\'appli"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Installer en mode privé"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Désinstaller l\'appli"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Installer"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne pas suggérer d\'application"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne pas suggérer d\'appli"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Épingler la prédiction"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"installer des raccourcis"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Permettre à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Installation de <xliff:g id="NAME">%1$s</xliff:g>… (<xliff:g id="PROGRESS">%2$s</xliff:g> terminés)"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> en cours de téléchargement, <xliff:g id="PROGRESS">%2$s</xliff:g> effectué(s)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> en attente d\'installation"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"L\'application <xliff:g id="NAME">%1$s</xliff:g> est archivée. Appuyez pour télécharger."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"L\'application <xliff:g id="NAME">%1$s</xliff:g> est archivée. Appuyez pour la télécharger et la restaurer."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Mise à jour de l\'appli requise"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"L\'appli correspondant à cette icône n\'est pas mise à jour. Vous pouvez la mettre à jour manuellement pour réactiver le raccourci ou supprimer l\'icône."</string>
<string name="dialog_update" msgid="2178028071796141234">"Modifier"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtre"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Échec : <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Espace privé"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Gardez les applications privées verrouillées et masquées."</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Appuyer pour ouvrir ou configurer"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privé"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Paramètres d\'Espace privé"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Verrouiller/Déverrouiller Espace privé"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privé, déverrouillé"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privé, verrouillé"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Verrouiller"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Transition vers Espace privé"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Installer applis"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Installer"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Installer des applis dans l\'espace privé"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Dépassement"</string>
</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 038821e..ac72802 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Os widgets están desactivados no modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atallo non está dispoñible"</string>
<string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Define <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como aplicación de inicio predeterminada en Configuración"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información da aplicación para %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Configuración de uso para %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Gardar parella de apps"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"O dispositivo non admite este emparellamento de aplicacións"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Desprega o dispositivo para usar este emparellamento de aplicacións"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Non está dispoñible o emparellamento de aplicacións"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Mantén premido un widget para movelo."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toca dúas veces un widget e manteno premido para movelo ou utiliza accións personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largo por %2$d de alto"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>: %2$d de largo por %3$d de alto"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Mantén premido o widget para movelo pola pantalla de inicio"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Engadir á pantalla de inicio"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Engadiuse o widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> á pantalla de inicio"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suxestións"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Indispensables para o día a día"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Novidades para ti"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Reláxate"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Acada os teus obxectivos para estar en forma"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Adiántate á meteoroloxía"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Tamén che pode interesar…"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Esenciais"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Noticias e revistas"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entretemento"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Redes sociais"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Suxestións personalizadas"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgets de <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> á dereita, busca e opcións á esquerda"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# atallo}other{# atallos}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Widgets do traballo"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Toma de notas"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Información útil ao teu alcance"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Se queres obter información sen abrir as aplicacións, podes engadir widgets á pantalla de inicio"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Engadir"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Engadir o widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Toca para cambiar a configuración do widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Entendido"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Cambiar configuración do widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Buscar aplicacións"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Cargando aplicacións…"</string>
@@ -84,8 +86,9 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Información da app"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instalar en privado"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Desinstalar aplicación"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instalar"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Non suxerir aplicación"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Non suxerir app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Fixar predición"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar atallos"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite a unha aplicación engadir atallos sen intervención do usuario."</string>
@@ -118,7 +121,7 @@
<string name="allow_rotation_title" msgid="7222049633713050106">"Permitir xirar a pantalla de inicio"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Ao xirar o teléfono"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"Puntos de notificacións"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"Activados"</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"Opción activada"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Desactivados"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"Necesítase acceso ás notificacións"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Para que se mostren os puntos de notificacións, activa as notificacións da aplicación <xliff:g id="NAME">%1$s</xliff:g>"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instalando <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> completado"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Descargando <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="PROGRESS">%2$s</xliff:g> completado)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Esperando para instalar <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> está no arquivo. Toca para descargar esta aplicación."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> está no arquivo. Toca para descargar e restaurar."</string>
<string name="dialog_update_title" msgid="114234265740994042">"É necesario actualizar a aplicación"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"A aplicación á que corresponde esta icona non está actualizada. Podes actualizala manualmente para activar de novo este atallo, ou ben quitar a icona."</string>
<string name="dialog_update" msgid="2178028071796141234">"Actualizar"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtra"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Erro: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Espazo privado"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Manter bloqueadas e ocultas as aplicacións privadas"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Toca para configuralo ou abrilo"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privado"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Configuración do espazo privado"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloquear ou desbloquear o espazo privado"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privado, desbloqueado."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privado, bloqueado."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Bloquear"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Transición ao espazo privado"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instalar as aplicacións"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalar"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalar as aplicacións no espazo privado"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Menú adicional"</string>
</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index ec6d994..74747d0 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"સ્ક્રીનને વિભાજિત કરો"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s માટે ઍપ માહિતી"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sના વપરાશ સંબંધિત સેટિંગ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ઍપની જોડી સાચવો"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"આ ડિવાઇસ પર, આ ઍપની જોડીને સપોર્ટ આપવામાં આવતો નથી"</string>
<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="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d પહોળાઈ X %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_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> વિજેટ, %2$d પહોળું x %3$d ઊંચું"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"વિજેટને હોમ સ્ક્રીનની આજુબાજુ ખસેડવા માટે, તેને ટચ કરીને થોડીવાર દબાવી રાખો"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"હોમ સ્ક્રીનમાં ઉમેરો"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"હોમ સ્ક્રીન પર <xliff:g id="WIDGET_NAME">%1$s</xliff:g> વિજેટ ઉમેર્યુ"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"સૂચનો"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"તમારી દૈનિક આવશ્યકતાઓ"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"તમારા માટે સમાચાર"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"તમારો આરામદાયક ઝોન"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"તમારા ફિટનેસ લક્ષ્યો પૂરા કરો"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"હવામાન વિશે અપ ટૂ ડેટ રહો"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"કદાચ તમને આ પણ પસંદ હોય"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"આવશ્યક"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ન્યૂઝ અને સામાયિકો"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"મનોરંજન"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"સામાજિક"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"તમારા માટે સૂચવેલી સેવાઓ"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>ની વિજેટ જમણે, શોધ અને વિકલ્પો ડાબે"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# વિજેટ}one{# વિજેટ}other{# વિજેટ}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# શૉર્ટકટ}one{# શૉર્ટકટ}other{# શૉર્ટકટ}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ઑફિસ"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"વાતચીતો"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"નોંધ લેવી"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"ઉપયોગી માહિતી તમારી આંગળીના ટેરવે"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ઍપને ખોલ્યા વિના માહિતી મેળવવા માટે, તમે તમારી હોમ સ્ક્રીનમાં વિજેટ ઉમેરી શકો છો"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"ઉમેરો"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> વિજેટ ઉમેરો"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"વિજેટના સેટિંગ બદલવા માટે ટૅપ કરો"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"સમજાઈ ગયું"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"અનઇન્સ્ટૉલ કરો"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ઍપની માહિતી"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ખાનગીમાં ઇન્સ્ટૉલ કરો"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ઍપ અનઇન્સ્ટૉલ કરો"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ઇન્સ્ટૉલ કરો"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ઍપ સૂચવશો નહીં"</string>
<string name="pin_prediction" msgid="4196423321649756498">"પૂર્વાનુમાનને પિન કરો"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ઇન્સ્ટૉલ કરી રહ્યાં છીએ, <xliff:g id="PROGRESS">%2$s</xliff:g> પૂર્ણ થયું"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ડાઉનલોડ કરી રહ્યાં છે, <xliff:g id="PROGRESS">%2$s</xliff:g> પૂર્ણ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>, ઇન્સ્ટૉલ થવાની રાહ જોઈ રહ્યું છે"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> આર્કાઇવ કરી છે. ડાઉનલોડ કરવા માટે ટૅપ કરો."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g>ને આર્કાઇવ કર્યું છે. ડાઉનલોડ અને રિસ્ટોર કરવા માટે ટૅપ કરો."</string>
<string name="dialog_update_title" msgid="114234265740994042">"ઍપને અપડેટ કરવી જરૂરી છે"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"આ આઇકન માટે ઍપ અપડેટ કરવામાં આવી નથી. તમે આ શૉર્ટકટ ફરી ચાલુ કરવા અથવા આઇકન કાઢી નાખવા માટે ઍપને મેન્યુઅલી અપડેટ કરી શકો છો."</string>
<string name="dialog_update" msgid="2178028071796141234">"અપડેટ કરો"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ફિલ્ટર કરો"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"નિષ્ફળ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"ખાનગી સ્પેસ"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"ખાનગી ઍપને લૉક કરેલી અને છુપાવેલી રાખો"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"સેટઅપ કરવા કે ખોલવા માટે ટૅપ કરો"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ખાનગી"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"ખાનગી સ્પેસના સેટિંગ"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"ખાનગી સ્પેસને લૉક/અનલૉક કરો"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ખાનગી સ્પેસ, અનલૉક કરેલી છે."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ખાનગી સ્પેસ, લૉક કરેલી છે."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"લૉક"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"ખાનગી સ્પેસ પર સ્થાનાંતરણ"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ઍપ ઇન્સ્ટૉલ કરો"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ઇન્સ્ટૉલ કરો"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"ખાનગી સ્પેસમાં ઍપ ઇન્સ્ટૉલ કરો"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ઓવરફ્લો"</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 8636ed1..6071935 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s के लिए ऐप्लिकेशन की जानकारी"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s के लिए खर्च की सेटिंग"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ऐप पेयर सेव करें"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"साथ में इस्तेमाल किए जा सकने वाले ये ऐप्लिकेशन, इस डिवाइस पर काम नहीं कर सकते"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट को होम स्क्रीन पर जोड़ा गया"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"सुझाव"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"रोज़ाना इस्तेमाल होने वाले ज़रूरी ऐप्लिकेशन"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"आपके लिए खबरें"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"आपके मनोरंजन के लिए"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"फ़िटनेस के लक्ष्य हासिल करें"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"मौसम की अप-टू-डेट जानकारी पाएं"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"शायद आपको ये भी पसंद आएं"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ज़रूरी ऐप्लिकेशन"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"खबरों और पत्रिकाओं वाले ऐप्लिकेशन"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"मनोरंजन से जुड़े ऐप्लिकेशन"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"सोशल मीडिया ऐप्लिकेशन"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"आपके लिए सुझाए गए"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> के विजेट दाईं ओर, खोज का विजेट और अन्य विकल्प बाईं ओर"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# विजेट}one{# विजेट}other{# विजेट}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# शॉर्टकट}one{# शॉर्टकट}other{# शॉर्टकट}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"वर्क विजेट"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"बातचीत"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"नोट बनाने से जुड़े विजेट"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"काम की जानकारी आसानी से पाएं"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ऐप्लिकेशन को खोले बिना उनकी जानकारी पाने के लिए, होम स्क्रीन पर विजेट जोड़े जा सकते हैं"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"जोड़ें"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट जोड़ें"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"विजेट की सेटिंग में बदलाव करने के लिए टैप करें"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"ठीक है"</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>
@@ -84,9 +86,10 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"अनइंस्टॉल करें"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ऐप्लिकेशन की जानकारी"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"निजी तौर पर इंस्टॉल करें"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ऐप्लिकेशन अनइंस्टॉल करें"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"इंस्टॉल करें"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ऐप्लिकेशन का सुझाव न दें"</string>
- <string name="pin_prediction" msgid="4196423321649756498">"सुझाए गए ऐप्लिकेशन को पिन करें"</string>
+ <string name="pin_prediction" msgid="4196423321649756498">"सुझाए गए ऐप पिन करें"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"शॉर्टकट इंस्टॉल करें"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"ऐप को उपयोगकर्ता के हस्तक्षेप के बिना शॉर्टकट जोड़ने देती है."</string>
<string name="permlab_read_settings" msgid="5136500343007704955">"होम स्क्रीन की सेटिंग और शॉर्टकट पढ़ने की अनुमति"</string>
@@ -123,7 +126,7 @@
<string name="title_missing_notification_access" msgid="7503287056163941064">"सूचना के ऐक्सेस की ज़रूरत है"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"सूचना बिंदु दिखाने के लिए, <xliff:g id="NAME">%1$s</xliff:g> के ऐप्लिकेशन सूचना चालू करें"</string>
<string name="title_change_settings" msgid="1376365968844349552">"सेटिंग बदलें"</string>
- <string name="notification_dots_service_title" msgid="4284221181793592871">"नई सूचनाएं बताने वाला गोल निशान दिखाएं"</string>
+ <string name="notification_dots_service_title" msgid="4284221181793592871">"सूचनाएं बताने वाले डॉट दिखाएं"</string>
<string name="developer_options_title" msgid="700788437593726194">"डेवलपर के लिए सेटिंग और टूल"</string>
<string name="auto_add_shortcuts_label" msgid="4926805029653694105">"होम स्क्रीन पर ऐप्लिकेशन के आइकॉन जोड़ें"</string>
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नए ऐप्लिकेशन के लिए"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल किया जा रहा है, <xliff:g id="PROGRESS">%2$s</xliff:g> पूरा हो गया"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड हो रहा है, <xliff:g id="PROGRESS">%2$s</xliff:g> पूरी हुई"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> के इंस्टॉल होने की प्रतीक्षा की जा रही है"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> को संग्रहित किया गया. डाउनलोड करने के लिए टैप करें."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> को संग्रहित किया गया. ऐप्लिकेशन को वापस लाने और डाउनलोड करने के लिए टैप करें."</string>
<string name="dialog_update_title" msgid="114234265740994042">"ऐप्लिकेशन को अपडेट करना ज़रूरी है"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"इस आइकॉन का ऐप्लिकेशन अपडेट नहीं है. इस शॉर्टकट को फिर से चालू करने या आइकॉन को हटाने के लिए, ऐप्लिकेशन को मैन्युअल रूप से अपडेट किया जा सकता है."</string>
<string name="dialog_update" msgid="2178028071796141234">"अपडेट करें"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"फ़िल्टर"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"पूरा नहीं हुआ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"प्राइवेट स्पेस"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"निजी ऐप्लिकेशन को लॉक करके और छिपाकर रखें"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"सेट अप करने या खोलने के लिए टैप करें"</string>
<string name="ps_container_title" msgid="4391796149519594205">"निजी"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"प्राइवेट स्पेस सेटिंग"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"प्राइवेट स्पेस को लॉक करें/अनलॉक करें"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"प्राइवेट स्पेस को अनलॉक किया गया."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"प्राइवेट स्पेस को लॉक किया गया."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"लॉक"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"प्राइवेट स्पेस की सेटिंग में बदलाव किया जा रहा है"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ऐप्लिकेशन इंस्टॉल करें"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"इंस्टॉल करें"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"प्राइवेट स्पेस में ऐप्लिकेशन इंस्टॉल करें"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ओवरफ़्लो"</string>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index f40252b..cf7a91a 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgeti su onemogućeni u Sigurnom načinu rada"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečac nije dostupan"</string>
<string name="home_screen" msgid="5629429142036709174">"Početni zaslon"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Postavite aplikaciju <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kao zadanu aplikaciju početnog zaslona u postavkama"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni zaslon"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke upotrebe za %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Spremi par aplikacija"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Taj par aplikacija nije podržan na ovom uređaju"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Otvorite uređaj da biste upotrebljavali ovaj par aplikacija"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Par aplikacija nije dostupan"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Dodirnite i zadržite da biste premjestili widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvaput dodirnite i zadržite pritisak da biste premjestili widget ili upotrijebite prilagođene radnje"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d širine i %2$d visine"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, širine %2$d, visine %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Dodirnite i zadržite widget da biste ga pomicali po početnom zaslonu"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Dodaj na početni zaslon"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> dodan je na početni zaslon"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Prijedlozi"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Vaše dnevne potrepštine"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Vijesti za vas"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Vaša zona za opuštanje"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Postignite svoje ciljeve u fitnesu"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Budite korak ispred vremenskih prilika"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Možda će vam se svidjeti i ovo"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Osnovno"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Vijesti i časopisi"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Zabava"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Društvene mreže"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Prijedlozi za vas"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> –widgeti zdesna, pretraživanje i opcije slijeva"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}one{# widget}few{# widgeta}other{# widgeta}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# prečac}one{# prečac}few{# prečaca}other{# prečaca}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Razgovori"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Pisanje bilježaka"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Korisne informacije nadohvat ruke"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Da biste dobili informacije bez otvaranja aplikacija, možete dodati widgete na početni zaslon"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodaj widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Dodirnite da biste promijenili postavke widgeta"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Shvaćam"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Promijenite postavke widgeta"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Pretraži aplikacije"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Učitavanje aplikacija…"</string>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Ukloni"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstaliraj"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Podaci o aplikaciji"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instaliranje u privatni profil"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instaliraj u privatno"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Deinstaliraj aplikaciju"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instaliraj"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne predlaži aplikaciju"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Prikvači predviđenu apl."</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instaliranje aplikacije <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> dovršeno"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Preuzimanje aplikacije <xliff:g id="NAME">%1$s</xliff:g>, dovršeno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Čekanje na instaliranje aplikacije <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Aplikacija <xliff:g id="NAME">%1$s</xliff:g> je arhivirana. Dodirnite za preuzimanje."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Aplikacija <xliff:g id="NAME">%1$s</xliff:g> je arhivirana. Dodirnite da biste je preuzeli i vratili."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Aplikacija se treba ažurirati"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Aplikacija ove ikone nije ažurirana. Možete ručno ažurirati da biste ponovo omogućili ovaj prečac ili uklonite ikonu."</string>
<string name="dialog_update" msgid="2178028071796141234">"Ažuriraj"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrirajte"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Nije uspjelo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privatni prostor"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Neka privatne aplikacije ostanu zaključane i skrivene"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Dodirnite da biste postavili ili otvorili"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privatno"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Postavke privatnog prostora"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Zaključavanje/otključavanje privatnog prostora"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privatno, otključano."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privatno, zaključano."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Zaključavanje"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Prelazak na privatni prostor"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instaliranje aplikacija"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalirajte"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instaliranje aplikacija u privatni prostor"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Dodatni izbornik"</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 6fc5f28..f306110 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"A modulok ki vannak kapcsolva Csökkentett módban"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"A gyorsparancs nem áll rendelkezésre"</string>
<string name="home_screen" msgid="5629429142036709174">"Kezdőképernyő"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"A(z) <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> appot a Beállításokban adhatja meg alapértelmezett kezdőalkalmazásként."</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Osztott képernyő"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Alkalmazásinformáció a következőhöz: %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"A(z) %1$s használati beállításai"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Alkalmazáspár mentése"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ezt az alkalmazáspárt nem támogatja az eszköz"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Hajtsa ki az eszközt az alkalmazáspár használatához"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Az alkalmazáspár nem áll rendelkezésre"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Tartsa lenyomva a modult az áthelyezéshez."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Modul áthelyezéséhez koppintson duplán, tartsa nyomva az ujját, vagy használjon egyéni műveleteket."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d széles és %2$d magas"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> modul"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> modul – %2$d széles, %3$d magas"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Tartsa lenyomva a modult a kezdőképernyőn való mozgatáshoz"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Hozzáadás a kezdőképernyőhöz"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> modul hozzáadva a kezdőképernyőhöz"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Javaslatok"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Legfontosabb napi dolgok"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Hírek Önnek"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Az Ön relaxáló zónája"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Elérheti kitűzött erőnléti céljait"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Mindig friss időjárás-információk"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Lehet, hogy ez is tetszeni fog"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Legfontosabbak"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Újságok és magazinok"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Szórakozás"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Közösségi"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Neked javasolt"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"A <xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-modulok a jobb, a kereső és a beállítások pedig a bal oldalon találhatók"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# modul}other{# modul}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# gyorsparancs}other{# gyorsparancs}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Munka"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Beszélgetések"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Jegyzetelés"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Hasznos információk egy koppintásnyira"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Ha az alkalmazások megnyitása nélkül szeretne információhoz jutni, felvehet modulokat a kezdőképernyőre"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Hozzáadás"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> modul hozzáadása"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Ide koppintva módosíthatja a modulbeállításokat"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Értem"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"A modulbeállítások módosítása"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Alkalmazások keresése"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Alkalmazások betöltése…"</string>
@@ -83,9 +85,10 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Törlés"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Eltávolítás"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Alkalmazásinfó"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Telepítés privátra"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Privát telepítés"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Alkalmazás eltávolítása"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Telepítés"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne javasoljon alkalmazást"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne javasoljon appot"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Várható kitűzése"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"parancsikonok telepítése"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül adjon hozzá parancsikonokat."</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Folyamatban van a(z) <xliff:g id="NAME">%1$s</xliff:g> telepítése, <xliff:g id="PROGRESS">%2$s</xliff:g> kész"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"A(z) <xliff:g id="NAME">%1$s</xliff:g> letöltése, <xliff:g id="PROGRESS">%2$s</xliff:g> kész"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"A(z) <xliff:g id="NAME">%1$s</xliff:g> telepítésre vár"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> archiválva. Koppintson a letöltéshez."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> archiválva. Koppintson a letöltéshez és a visszaállításhoz."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Alkalmazásfrissítés szükséges"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Az ikonhoz tartozó alkalmazás nincs frissítve. A parancsikon újbóli engedélyezéséhez frissítse az alkalmazást, vagy távolítsa ez az ikont."</string>
<string name="dialog_update" msgid="2178028071796141234">"Frissítés"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Szűrő"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Sikertelen: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privát terület"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Privát alkalmazások zárolásának és rejtve tartásának fenntartása"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Koppintson a beállításhoz vagy a megnyitáshoz"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privát"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Privát terület beállításai"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Privát terület zárolása/zárolásának feloldása"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privát, feloldott."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privát, zárolt."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Zárolás"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Átállás privát területre…"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Alkalmazástelepítés"</string>
- <string name="ps_add_button_content_description" msgid="3254274107740952556">"Alkalmazások telepítése magánterületre"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Túlcsordulás"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Telepítés"</string>
+ <string name="ps_add_button_content_description" msgid="3254274107740952556">"Alkalmazások telepítése privát területre"</string>
</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index ef77cd9..2d345f2 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Տրոհել էկրանը"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Տեղեկություններ %1$s հավելվածի մասին"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Օգտագործման կարգավորումներ (%1$s)"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Պահել հավելվ. զույգը"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Հավելվածների զույգը չի աջակցվում այս սարքում"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> վիջեթն ավելացվել է հիմնական էկրանին"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Առաջարկներ"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Ամենաանհրաժեշտը յուրաքանչյուր օրվա համար"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Նորություններ ձեզ համար"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Ձեր հանգստի գոտին"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Հասեք ձեր ֆիթնես նպատակներին"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Եղեք տեղեկացված եղանակի մասին"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Ձեզ կարող է դուր գալ"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Հիմնական"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Նորություններ և ամսագրեր"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Զվարճանք"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Սոցցանցեր"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Առաջարկում ենք"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"«<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>» հավելվածի վիջեթներն աջ կողմում են, իսկ որոնման դաշտը և կարգավորումները՝ ձախ կողմում"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# վիջեթ}one{# վիջեթ}other{# վիջեթ}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# դյուրանցում}one{# դյուրանցում}other{# դյուրանցում}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Աշխատանքային"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Զրույցներ"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Նշումների ստեղծում"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Բոլոր կարևոր տեղեկությունները՝ ձեռքի տակ"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Ավելացրեք վիջեթներ ձեր հիմնական էկրանին, որպեսզի տեղեկություններ ստանաք՝ առանց հավելվածները բացելու։"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Ավելացնել"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Ավելացնել <xliff:g id="WIDGET_NAME">%1$s</xliff:g> վիջեթը"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Հպեք՝ վիջեթի կարգավորումները փոփոխելու համար"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Եղավ"</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>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Հեռացնել"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Ապատեղադրել"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Հավելվածի մասին"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Տեղադրել անձնականում"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Տեղադրել մասնավորում"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Ապատեղադրել հավելվածը"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Տեղադրել"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Չառաջարկել"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Ամրացնել առաջարկվող հավելվածը"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> հավելվածը տեղադրվում է, կատարված է <xliff:g id="PROGRESS">%2$s</xliff:g>-ը"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>–ի ներբեռնում (<xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>-ի տեղադրման սպասում"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> հավելվածն արխիվացված է։ Հպեք՝ ներբեռնելու համար:"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> հավելվածն արխիվացված է։ Հպեք՝ ներբեռնելու և վերականգնելու համար։"</string>
<string name="dialog_update_title" msgid="114234265740994042">"Պահանջվում է թարմացնել հավելվածը"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Հավելվածը հնացել է։ Թարմացրեք այն ձեռքով, որպեսզի շարունակեք օգտագործել դյուրանցումը, կամ հեռացրեք հավելվածի պատկերակը։"</string>
<string name="dialog_update" msgid="2178028071796141234">"Թարմացնել"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Զտեք"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Չհաջողվեց կատարել գործողությունը (<xliff:g id="WHAT">%1$s</xliff:g>)"</string>
<string name="private_space_label" msgid="2359721649407947001">"Անձնական տարածք"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Անձնական հավելվածները պահեք կողպված և թաքցված"</string>
- <string name="ps_container_title" msgid="4391796149519594205">"Անձնական"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Հպեք կարգավորելու կամ բացելու համար"</string>
+ <string name="ps_container_title" msgid="4391796149519594205">"Մասնավոր"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Անձնական տարածքի կարգավորումներ"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Կողպել/ապակողպել մասնավոր տարածքը"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Անձնական, ապակողպված է։"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Անձնական, կողպված է։"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Կողպում"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Անցում մասնավոր տարածք"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Հավելվածների տեղադրում"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Տեղադրել"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Հավելվածների տեղադրում անձնական տարածքում"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Լրացուցիչ ընտրացանկ"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 0fdae96..9ced9f4 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget dinonaktifkan dalam mode Aman"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
<string name="home_screen" msgid="5629429142036709174">"Layar utama"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Jadikan <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> sebagai aplikasi layar utama default di Setelan"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Layar terpisah"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Info aplikasi untuk %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Setelan penggunaan untuk %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Simpan pasangan aplikasi"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Pasangan aplikasi ini tidak didukung di perangkat ini"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Bentangkan perangkat untuk menggunakan pasangan aplikasi ini"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Pasangan aplikasi tidak tersedia"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Sentuh lama untuk memindahkan widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Ketuk dua kali & tahan untuk memindahkan widget atau gunakan tindakan khusus."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"lebar %1$d x tinggi %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, lebar %2$d dan tinggi %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Sentuh lama widget untuk memindah-mindahkannya di layar utama"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Tambahkan ke layar utama"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> ditambahkan ke layar utama"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Saran"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Kebutuhan Harian Anda"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Berita untuk Anda"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Zona Nyaman Anda"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Capai Target Kebugaran Anda"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Tetap Waspada Menghadapi Cuaca"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Anda Mungkin Juga Suka"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Penting"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Berita & majalah"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Hiburan"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sosial"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Disarankan untuk Anda"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widget <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> di bagian kanan, penelusuran dan opsi di bagian kiri"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widget}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# pintasan}other{# pintasan}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Kerja"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Percakapan"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Pembuatan catatan"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Info bermanfaat mudah dilihat"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Untuk mendapatkan info tanpa membuka aplikasi, Anda dapat menambahkan widget ke layar utama"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Tambahkan"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Tambahkan widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Ketuk untuk mengubah setelan widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Oke"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Ubah setelan widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Telusuri aplikasi"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Memuat aplikasi…"</string>
@@ -83,9 +85,10 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Hapus"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstal"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Info aplikasi"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instal secara pribadi"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instal di ruang privasi"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Uninstal aplikasi"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instal"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Jangan sarankan aplikasi"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Jangan sarankan apl"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Pin Prediksi"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"memasang pintasan"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Mengizinkan aplikasi menambahkan pintasan tanpa campur tangan pengguna."</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> sedang diinstal, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> sedang didownload, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> menunggu dipasang"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> diarsipkan. Ketuk untuk mendownload."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> diarsipkan. Ketuk untuk mendownload dan memulihkan."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Aplikasi perlu diupdate"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Aplikasi untuk ikon ini belum diupdate. Anda dapat mengupdate secara manual untuk mengaktifkan kembali pintasan ini, atau hapus ikon."</string>
<string name="dialog_update" msgid="2178028071796141234">"Update"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Aktifkan lagi"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Gagal: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Ruang pribadi"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Kunci dan sembunyikan aplikasi pribadi"</string>
- <string name="ps_container_title" msgid="4391796149519594205">"Pribadi"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Ruang privasi"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Ketuk untuk menyiapkan atau membuka"</string>
+ <string name="ps_container_title" msgid="4391796149519594205">"Privasi"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Setelan Ruang Pribadi"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Kunci/Buka Kunci Ruang Pribadi"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Pribadi, tidak terkunci."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Pribadi, dikunci."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Kunci"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Ruang Pribadi Bertransisi"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instal aplikasi"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instal"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instal aplikasi ke Ruang Pribadi"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Menu tambahan"</string>
</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 51c2e52..2ab7817 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Græjur eru óvirkar í öruggri stillingu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Flýtileið er ekki tiltæk"</string>
<string name="home_screen" msgid="5629429142036709174">"Heim"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Stilltu <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> sem sjálfgefið heimaforrit í stillingunum"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skipta skjá"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Upplýsingar um forrit fyrir %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Notkunarstillingar fyrir %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Vista forritapar"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Þetta forritapar er ekki stutt í þessu tæki"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Opnaðu tæki til að nota þetta forritapar"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Forritapar er ekki í boði"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Haltu fingri á græju til að færa hana."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Ýttu tvisvar og haltu fingri á græju til að færa hana eða notaðu sérsniðnar aðgerðir."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d á breidd og %2$d á hæð"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Græjan <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Græjan <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d breið og %3$d há"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Haltu fingri á græjunni til að hreyfa hana um heimaskjáinn"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Bæta á heimaskjá"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> græju bætt við heimaskjá"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Tillögur"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Daglegar nauðsynjar"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Fréttir fyrir þig"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Slakaðu á"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Náðu hreyfingarmarkmiðunum þínum"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Vertu einu skrefi á undan veðrinu"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Þú gætir einnig haft áhuga á"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Það nauðsynlegasta"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Fréttir og tímarit"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Afþreying"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Samfélag"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Tillögur fyrir þig"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-græjur til hægri, leit og valkostir til vinstri"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# græja}one{# græja}other{# græjur}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# flýtileið}one{# flýtileið}other{# flýtileiðir}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Vinna"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Samtöl"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Glósugerð"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Gagnlegar upplýsingar innan seilingar"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Þú getur bætt við græjum á heimaskjáinn til að fá upplýsingar án þess að opna forrit"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Bæta við"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Bæta græjunni <xliff:g id="WIDGET_NAME">%1$s</xliff:g> við"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Ýttu til að breyta græjustillingum"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Ég skil"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Breyta græjustillingum"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Leita í forritum"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Hleður forrit…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Fjarlægja"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Forritsupplýsingar"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Setja upp á lokuðum prófíl"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Fjarlægja forrit"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Setja upp"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Ekki fá tillögu að forriti"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Festa tillögu"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Setur upp <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> lokið"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> í niðurhali, <xliff:g id="PROGRESS">%2$s</xliff:g> lokið"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> bíður uppsetningar"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> er í geymslu. Ýttu til að sækja."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> er í geymslu. Ýttu til að sækja og endurheimta."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Uppfæra þarf forritið"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Forritið fyrir þetta tákn er ekki uppfært. Þú getur uppfært það handvirkt til að kveikja aftur á þessari flýtileið eða fjarlægt táknið."</string>
<string name="dialog_update" msgid="2178028071796141234">"Uppfæra"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Ljúka hléi"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Sía"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Mistókst: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Einkarými"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Haltu einkaforritum læstum og földum"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Leynirými"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Ýttu til að setja upp eða opna"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Lokað"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Stillingar einkarýmis"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Læsa leynirými/taka leynirými úr lás"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Lokað, ólæst."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Lokað, læst."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Læsa"</string>
- <string name="ps_container_transition" msgid="8667331812048014412">"Einkarými að breytast"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Setja upp forrit"</string>
+ <string name="ps_container_transition" msgid="8667331812048014412">"Leynirými að breytast"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Setja upp"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Setja upp forrit í leynirými"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Yfirflæði"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 0c76ba9..a59de6c 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget disabilitati in modalità provvisoria"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"La scorciatoia non è disponibile"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Imposta <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> come app iniziale predefinita nelle Impostazioni"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Schermo diviso"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informazioni sull\'app %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Impostazioni di utilizzo per %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Salva coppia di app"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Questa coppia di app non è supportata su questo dispositivo"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Apri il dispositivo per usare questa coppia di app"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"La coppia di app non è disponibile"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Tocca e tieni premuto per spostare un widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Tocca due volte e tieni premuto per spostare un widget o per usare le azioni personalizzate."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d di larghezza per %2$d di altezza"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d di larghezza per %3$d di altezza"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Tocca e tieni premuto il widget per spostarlo nella schermata Home"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Aggiungi alla schermata Home"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> aggiunto alla schermata Home"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggerimenti"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"L\'essenziale ogni giorno"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Notizie per te"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Il tuo angolo di tranquillità"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Raggiungi i tuoi obiettivi di fitness"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Non perderti le previsioni meteo"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Ti potrebbero anche piacere"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essenziali"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Notizie e riviste"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Intrattenimento"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Social"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Consigliati per te"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widget di <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> a destra, ricerca e opzioni a sinistra"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widget}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# scorciatoia}other{# scorciatoie}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Lavoro"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversazioni"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Aggiunta di note"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Informazioni utili a portata di mano"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Per ricevere informazioni senza aprire le app, puoi aggiungere dei widget alla schermata Home"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Aggiungi"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Aggiungi widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tocca per modificare le impostazioni del widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Modifica le impostazioni del widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Cerca nelle app"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Caricamento delle app…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Disinstalla"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Informazioni app"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Installa in privato"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Disinstalla app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Installa"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Non suggerire app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Blocca previsione"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Installazione di <xliff:g id="NAME">%1$s</xliff:g>, completamento: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Download di <xliff:g id="NAME">%1$s</xliff:g> in corso, <xliff:g id="PROGRESS">%2$s</xliff:g> completato"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> in attesa di installazione"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"App <xliff:g id="NAME">%1$s</xliff:g> archiviata. Tocca per scaricare."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"App <xliff:g id="NAME">%1$s</xliff:g> archiviata. Tocca per scaricare e ripristinare."</string>
<string name="dialog_update_title" msgid="114234265740994042">"È necessario aggiornare l\'app"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"L\'app relativa a questa icona non è aggiornata. Puoi eseguire manualmente l\'aggiornamento per riattivare questa scorciatoia oppure rimuovere l\'icona."</string>
<string name="dialog_update" msgid="2178028071796141234">"Aggiorna"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtra"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Operazione non riuscita: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Spazio privato"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Mantieni le app private bloccate e nascoste"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Tocca per configurare o aprire"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privato"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Impostazioni dello Spazio privato"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Blocca/sblocca Spazio privato"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privato, sbloccato."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privato, bloccato."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Blocca"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Transizione dello Spazio privato in corso…"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Installa app"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Installa"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Installa le app su spazi privati"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Extra"</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 461a25e..89a0c8a 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"מסך מפוצל"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"פרטים על האפליקציה %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"הגדרות שימוש ב-%1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"שמירת צמד אפליקציות"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"צמד האפליקציות הזה לא נתמך במכשיר הזה"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"הווידג\'ט <xliff:g id="WIDGET_NAME">%1$s</xliff:g> נוסף למסך הבית"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"הצעות"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"הפריטים היומיומיים שלך"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"חדשות בשבילך"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"המקום שלך לרגיעה"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"השגת יעדי הכושר שלך"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"התעדכנות במזג האוויר"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"אולי יעניין אותך גם"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"הכי חשוב"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"חדשות וכתבי עת"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"בידור"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"רשתות חברתיות"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"הצעות בשבילך"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ווידג\'טים מימין, חיפוש ואפשרויות משמאל"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{ווידג\'ט אחד}one{# ווידג\'טים}two{# ווידג\'טים}other{# ווידג\'טים}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{קיצור דרך אחד}one{# קיצורי דרך}two{# קיצורי דרך}other{# קיצורי דרך}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"עבודה"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"שיחות"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"כתיבת הערות"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"קבלת מידע שימושי בהקשה"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"רוצה לקבל מידע בלי לפתוח אפליקציות? אפשר להוסיף ווידג\'טים למסך הבית"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"הוספה"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"הוספת הווידג\'ט <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"אפשר לשנות את הגדרות הווידג\'ט בהקשה"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"הבנתי"</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>
@@ -82,8 +84,9 @@
<string name="all_apps_button_work_label" msgid="7270707118948892488">"רשימת אפליקציות עבודה"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"הסרה"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"להסרת התקנה"</string>
- <string name="app_info_drop_target_label" msgid="692894985365717661">"פרטי אפליקציה"</string>
+ <string name="app_info_drop_target_label" msgid="692894985365717661">"פרטי האפליקציה"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"התקנה במרחב הפרטי"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"הסרת האפליקציה"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"התקנה"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"בלי להציע את האפליקציה"</string>
<string name="pin_prediction" msgid="4196423321649756498">"הצמדת החיזוי"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> בתהליך התקנה, <xliff:g id="PROGRESS">%2$s</xliff:g> הושלמו"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"הורדת <xliff:g id="NAME">%1$s</xliff:g> מתבצעת, <xliff:g id="PROGRESS">%2$s</xliff:g> הושלמו"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"מחכה להתקנה של <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"אפליקציית <xliff:g id="NAME">%1$s</xliff:g> הועברה לארכיון. יש להקיש כדי להוריד."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"אפליקציית <xliff:g id="NAME">%1$s</xliff:g> הועברה לארכיון. אפשר להקיש כדי להוריד ולשחזר אותה."</string>
<string name="dialog_update_title" msgid="114234265740994042">"נדרש עדכון לאפליקציה"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"האפליקציה של הסמל הזה לא מעודכנת. אפשר לעדכן אותה ידנית כדי להפעיל מחדש את קיצור הדרך הזה, או להסיר את הסמל."</string>
<string name="dialog_update" msgid="2178028071796141234">"עדכון"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"סינון"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"הפעולה נכשלה: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"מרחב פרטי"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"נעילה והסתרה של אפליקציות פרטיות"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"יש להקיש כדי להגדיר או לפתוח"</string>
<string name="ps_container_title" msgid="4391796149519594205">"פרטי"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"הגדרות המרחב הפרטי"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"נעילה או ביטול הנעילה של המרחב הפרטי"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"פרטי, פתוח."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"פרטי, נעול."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"נעילה"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"מעבר למרחב הפרטי"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"התקנת אפליקציות"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"התקנה"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"התקנת אפליקציות במרחב הפרטי"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"אפשרויות נוספות"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 5b3e221..b48c7be 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割画面"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s のアプリ情報"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s の使用設定"</string>
<string name="save_app_pair" msgid="5647523853662686243">"アプリのペア設定を保存"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"このデバイスは、このアプリのペア設定に対応していません"</string>
<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="widget_dims_format" msgid="2370757736025621599">"%1$dx%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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"「<xliff:g id="WIDGET_NAME">%1$s</xliff:g>」ウィジェットをホーム画面に追加しました"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"候補"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"生活必需品"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"あなたへのおすすめニュース"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"休憩エリア"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"フィットネスの目標を達成"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"天気予報"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"あなたへのおすすめ"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"基本"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ニュース&雑誌"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"エンタメ"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"ソーシャル"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"おすすめ"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> のウィジェットは右側に、検索とオプションは左側にあります"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# 件のウィジェット}other{# 件のウィジェット}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# 件のショートカット}other{# 件のショートカット}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"仕事用"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"会話"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"メモ"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"ウィジェットで情報を得る"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ホーム画面にウィジェットを追加すると、アプリを開かずに情報を入手できます"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"追加"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>ウィジェットを追加"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"タップしてウィジェットの設定を変更する"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"アンインストール"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"アプリ情報"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"非公開インストール"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"アプリをアンインストール"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"インストール"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"アプリを表示しない"</string>
<string name="pin_prediction" msgid="4196423321649756498">"アプリの候補を固定"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> をインストールしています: <xliff:g id="PROGRESS">%2$s</xliff:g> 完了"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>をダウンロード中、<xliff:g id="PROGRESS">%2$s</xliff:g>完了"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>のインストール待ち"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> はアーカイブ済みです。ダウンロードするにはタップします。"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g>はアーカイブ済みです。ダウンロードして復元するには、タップしてください。"</string>
<string name="dialog_update_title" msgid="114234265740994042">"アプリの更新が必要"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"このアイコンのアプリは更新されていません。手動で更新して、このショートカットを再度有効にできます。また、アイコンを削除することもできます。"</string>
<string name="dialog_update" msgid="2178028071796141234">"更新"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"フィルタ"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"失敗: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"プライベート スペース"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"プライベート アプリをロック / 非表示状態に保つ"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"設定したり開いたりするにはタップしてください"</string>
<string name="ps_container_title" msgid="4391796149519594205">"プライベート"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"プライベート スペースの設定"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"プライベート スペースをロック / ロック解除する"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"非公開で、ロックが解除されています。"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"非公開で、ロックされています。"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ロック"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"プライベート スペース移行中"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"アプリをインストールする"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"インストール"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"プライベート スペースにアプリをインストールします"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"オーバーフロー"</string>
</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index ae3b1e6..76b8b5d 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -27,27 +27,30 @@
<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>-ის დაყენება ნაგულისხმევ Home აპად პარამეტრებში"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ეკრანის გაყოფა"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-ის აპის ინფო"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"გამოყენების პარამეტრები %1$s-ისთვის"</string>
<string name="save_app_pair" msgid="5647523853662686243">"აპთა წყვილის შენახვა"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ამ მოწყობილობაზე აღნიშნული აპთა წყვილი არ არის მხარდაჭერილი"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ვიჯეტი დამატებულია მთავარ ეკრანზე"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"შეთავაზებები"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"თქვენი ყოველდღიური საჭირო აპები"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"News თქვენთვის"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"განტვირთვის ადგილი"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"მიაღწიეთ ფიტნეს-მიზნებს"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"მიიღეთ ინფორმაცია წინასწარ ამინდის შესახებ"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"ასევე შეიძლება მოგეწონოთ"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"უმნიშვნელოვანესები"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ახალი ამბები და ჟურნალები"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"გართობა"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"სოციალური"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"თქვენთვის შემოთავაზებული"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ვიჯეტები მდებარეობს მარჯვნივ, ძებნა და პარამეტრები — მარცხნივ"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# ვიჯეტი}other{# ვიჯეტი}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# მალსახმობი}other{# მალსახმობი}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"სამსახური"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"მიმოწერები"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"ჩანიშვნა"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"ადვილად მისაწვდომი სასარგებლო ინფორმაცია"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"იმისთვის, რომ ინფორმაცია აპების გაუხსნელად მიიღოთ, შეგიძლიათ, მთავარ ეკრანზე ვიჯეტები დაამატოთ"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"დამატება"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ვიჯეტის დამატება"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"შეეხეთ ვიჯეტის პარამეტრების შესაცვლელად"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"გასაგებია"</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>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"ამოშლა"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"დეინსტალაცია"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"აპის შესახებ"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"პირადში ინსტალაცია"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"კერძოში ინსტალაცია"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"აპის დეინსტალაცია"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ინსტალაცია"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"არ შემომთავაზო აპი"</string>
<string name="pin_prediction" msgid="4196423321649756498">"ჩამაგრების პროგნოზირება"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"ინსტალირდება <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> დასრულებულია"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"მიმდინარეობს <xliff:g id="NAME">%1$s</xliff:g>-ის ჩამოტვირთვა, <xliff:g id="PROGRESS">%2$s</xliff:g> დასრულდა"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ელოდება ინსტალაციას"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> დაარქივებულია. შეეხეთ ჩამოსატვირთად."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> დაარქივებულია. შეეხეთ გადმოსაწერად და აღსადგენად."</string>
<string name="dialog_update_title" msgid="114234265740994042">"საჭიროა აპის განახლება"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"ამ ხატულის აპი განახლებული არ არის. შეგიძლიათ, ხელით განაახლოთ ამ მალსახმობის ხელახლა გასააქტიურებლად, ან ამოშალოთ ხატულა."</string>
<string name="dialog_update" msgid="2178028071796141234">"განახლება"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ფილტრი"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"ვერ მოხერხდა: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"პირადი სივრცე"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"პირადი აპების ჩაკეტვა და დამალვა"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"დასაყენებლად ან გასახსნელად შეეხეთ"</string>
<string name="ps_container_title" msgid="4391796149519594205">"პირადი"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"პირადი სივრცის პარამეტრები"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"პირადი სივრცის ჩაკეტვა/განბლოკვა"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"პირადი (განბლოკილი)."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"პირადი (ჩაკეტილი)."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ჩაკეტვა"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"პირად სივრცეზე გადასვლა"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"აპების ინსტალაცია"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ინსტალაცია"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"კერძო სივრცეში აპების ინსტალაცია"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"გადავსება"</string>
</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 7038483..95d4420 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлу"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s қолданбасы туралы ақпарат"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s пайдалану параметрлері"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Қолданбаларды жұптау әрекетін сақтау"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Бұл құрылғы қолданбаларды жұптау функциясын қолдамайды."</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджеті негізгі экранға енгізілді."</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Ұсыныстар"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Күнделікті маңызды виджеттеріңіз"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Сізге арналған жаңалықтар"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Жанға жайлы жер"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Денені шынықтыру бойынша қойған мақсаттарыңызға жетіңіз"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Ауа райын алдын ала біліп отырыңыз"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Сізге мыналар да ұнауы мүмкін"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Ең қажетті"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Жаңалықтар мен журналдар"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Ойын-сауық"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Қоғам"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Сізге ұсынылғандар"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> виджеттері оң жақта, іздеу мен опциялар сол жақта"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# виджет}other{# виджет}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# таңбаша}other{# таңбаша}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Жұмыс виджеттері"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Әңгімелер"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Ескертпе жазу"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Саусақпен түртсеңіз болғаны – пайдалы ақпарат көз алдыңызда"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Қолданбаларды ашпай-ақ ақпарат алу үшін негізгі экранға тиісті виджеттерді қосыңыз."</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Қосу"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Виджет (<xliff:g id="WIDGET_NAME">%1$s</xliff:g>) қосу"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Виджет параметрлерін өзгерту үшін түртіңіз."</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Түсінікті"</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>
@@ -83,10 +85,11 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Алып тастау"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Жою"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Қолданба ақпараты"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Жеке профильге орнату"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Құпия профильге орнату"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Қолданбаны жою"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Орнату"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Қолданба ұсынбау"</string>
- <string name="pin_prediction" msgid="4196423321649756498">"Болжанған қолданбаны бекіту"</string>
+ <string name="pin_prediction" msgid="4196423321649756498">"Болжамды бекіту"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"таңбаша орнату"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Қолданбаға пайдаланушының қатысуынсыз төте пернелерді қосу мүмкіндігін береді."</string>
<string name="permlab_read_settings" msgid="5136500343007704955">"негізгі экран параметрлері мен таңбашаларын оқу"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> орнатылуда, <xliff:g id="PROGRESS">%2$s</xliff:g> аяқталды"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> жүктелуде, <xliff:g id="PROGRESS">%2$s</xliff:g> аяқталды"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> орнату күтілуде"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> мұрағатталды. Жүктеп алу үшін түртіңіз."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> мұрағатталды. Жүктеп алу және қалпына келтіру үшін түртіңіз."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Қолданбаны жаңарту қажет"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Осы белгіше үшін қолданба жаңартылмаған. Оны қолмен жаңартып, осы таңбашаны қайта іске қоса аласыз немесе белгішені өшіріп тастаңыз."</string>
<string name="dialog_update" msgid="2178028071796141234">"Жаңарту"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Қайта қосу"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Сүзгі"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Қате шықты: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Жеке бөлме"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Құпия кеңістіктегі қолданбаларды құлыптаулы және жасырын күйде қалдырыңыз."</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Құпия кеңістік"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Реттеу немесе ашу үшін түртіңіз"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Жеке"</string>
- <string name="ps_container_settings" msgid="6059734123353320479">"Жеке бөлме параметрлері"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Жеке бөлмені құлыптау/оның құлпын ашу"</string>
+ <string name="ps_container_settings" msgid="6059734123353320479">"Құпия кеңістік параметрлері"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Құпия (құлыпталмаған)."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Құпия (құлыптаулы)."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Құлыптау"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Жеке бөлмеге өту"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Қолданбалар орнату"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Орнату"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Қолданбаларды \"Құпия кеңістікке\" орнатыңыз."</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Қосымша мәзір"</string>
</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 1297dee..5c71276 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"មុខងារបំបែកអេក្រង់"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"ព័ត៌មានកម្មវិធីសម្រាប់ %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"ការកំណត់ការប្រើប្រាស់សម្រាប់ %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"រក្សាទុកគូកម្មវិធី"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"មិនអាចប្រើគូកម្មវិធីនេះនៅលើឧបករណ៍នេះបានទេ"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"បានបញ្ចូលធាតុក្រាហ្វិក <xliff:g id="WIDGET_NAME">%1$s</xliff:g> ទៅអេក្រង់ដើម"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"ការណែនាំ"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"វត្ថុចាំបាច់ប្រចាំថ្ងៃរបស់អ្នក"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"ព័ត៌មានសម្រាប់អ្នក"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"តំបន់បន្ធូរអារម្មណ៍របស់អ្នក"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"សម្រេចគោលដៅហាត់ប្រាណរបស់អ្នក"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"ទទួលបានដំណឹងជាមុនអំពីអាកាសធាតុ"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"អ្នកក៏អាចនឹងចូលចិត្ត"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"សំខាន់"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ព័ត៌មាន និងទស្សនាវដ្ដី"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"កម្សាន្ត"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"សង្គម"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"ណែនាំជូនអ្នក"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"ធាតុក្រាហ្វិក <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> នៅខាងស្ដាំ ការស្វែងរក និងជម្រើសនៅខាងឆ្វេង"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{ធាតុក្រាហ្វិក #}other{ធាតុក្រាហ្វិក #}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{ផ្លូវកាត់ #}other{ផ្លូវកាត់ #}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ការងារ"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"ការសន្ទនា"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"ការកត់ត្រា"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"ទទួលបានព័ត៌មានដែលមានប្រយោជន៍យ៉ាងងាយស្រួល"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ដើម្បីទទួលបានព័ត៌មានដោយមិនចាំបាច់បើកកម្មវិធី អ្នកអាចបញ្ចូលធាតុក្រាហ្វិកទៅក្នុងអេក្រង់ដើមរបស់អ្នក"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"បញ្ចូល"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"បញ្ចូលធាតុក្រាហ្វិក <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ចុចដើម្បីប្ដូរការកំណត់ធាតុក្រាហ្វិក"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"យល់ហើយ"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"លុប"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ព័ត៌មានកម្មវិធី"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ដំឡើងជាលក្ខណៈឯកជន"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"លុបកម្មវិធី"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ដំឡើង"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"កុំណែនាំកម្មវិធី"</string>
<string name="pin_prediction" msgid="4196423321649756498">"ខ្ទាស់ការព្យាករ"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"កំពុងដំឡើង <xliff:g id="NAME">%1$s</xliff:g>, បានបញ្ចប់ <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"កំពុងដោនឡូត <xliff:g id="NAME">%1$s</xliff:g> បានបញ្ចប់ <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> កំពុងរង់ចាំការដំឡើង"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> ត្រូវបានទុកក្នុងបណ្ណសារ។ សូមចុចដើម្បីទាញយក។"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> ត្រូវបានទុកក្នុងបណ្ណសារ។ សូមចុចដើម្បីទាញយក និងស្ដារ។"</string>
<string name="dialog_update_title" msgid="114234265740994042">"តម្រូវឱ្យមានកំណែកម្មវិធីថ្មី"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"កម្មវិធីសម្រាប់រូបតំណាងនេះមិនត្រូវបានដំឡើងកំណែទេ។ អ្នកអាចដំឡើងកំណែដោយផ្ទាល់ ដើម្បីបើកផ្លូវកាត់នេះឡើងវិញ ឬលុបរូបតំណាងនេះ។"</string>
<string name="dialog_update" msgid="2178028071796141234">"ដំឡើងកំណែ"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"តម្រង"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"បានបរាជ័យ៖ <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"បន្ទប់ឯកជន"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"រក្សាកម្មវិធីឯកជនឱ្យនៅជាប់សោ និងលាក់"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"ចុចដើម្បីរៀបចំ ឬបើក"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ឯកជន"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"ការកំណត់ Private Space"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"ចាក់សោ/ដោះសោ Private Space"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ឯកជន បានដោះសោ។"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ឯកជន ជាប់សោ។"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ចាក់សោ"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"ការផ្លាស់ប្ដូរ Private Space"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ដំឡើងកម្មវិធី"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ដំឡើង"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"ដំឡើងកម្មវិធីទៅលំហឯកជន"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ម៉ឺនុយបន្ថែម"</string>
</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index fa9a332..931ca30 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ಗಾಗಿ ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ಗೆ ಸಂಬಂಧಿಸಿದ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ಆ್ಯಪ್ ಪೇರ್ ಸೇವ್ ಮಾಡಿ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ಈ ಆ್ಯಪ್ ಜೋಡಿಯು ಈ ಸಾಧನದಲ್ಲಿ ಬೆಂಬಲಿತವಾಗಿಲ್ಲ"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"ಹೋಮ್ಸ್ಕ್ರೀನ್ಗೆ <xliff:g id="WIDGET_NAME">%1$s</xliff:g> ವಿಜೆಟ್ ಅನ್ನು ಸೇರಿಸಲಾಗಿದೆ"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"ಸಲಹೆಗಳು"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"ನಿಮ್ಮ ದೈನಂದಿನ ಎಸೆನ್ಶಿಯಲ್ಗಳು"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"ನಿಮಗಾಗಿ ಸುದ್ದಿ"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"ನೀವು ವಿಶ್ರಾಂತಿ ಪಡೆಯುವ ಸ್ಥಳ"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"ನಿಮ್ಮ ಫಿಟ್ನೆಸ್ ಗುರಿಗಳನ್ನು ಸಾಧಿಸಿ"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"ಹವಾಮಾನದ ಕುರಿತು ಮುಂಚೆಯೇ ಅಪ್ಡೇಟ್ ಆಗಿರಿ"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"ನಿಮಗೆ ಇವು ಕೂಡ ಇಷ್ಟವಾಗಬಹುದು"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ಅಗತ್ಯತೆಗಳು"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ಸುದ್ದಿ ಮತ್ತು ನಿಯತಕಾಲಿಕೆಗಳು"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"ಮನರಂಜನೆ"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"ಸಾಮಾಜಿಕ"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"ನಿಮಗಾಗಿ ಸೂಚಿಸಲಾಗಿರುವುದು"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"ಬಲಭಾಗದಲ್ಲಿ <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ವಿಜೆಟ್ಗಳು, ಎಡಭಾಗದಲ್ಲಿ ಹುಡುಕಾಟ ಮತ್ತು ಆಯ್ಕೆಗಳು"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# ವಿಜೆಟ್}one{# ವಿಜೆಟ್ಗಳು}other{# ವಿಜೆಟ್ಗಳು}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# ಶಾರ್ಟ್ಕಟ್}one{# ಶಾರ್ಟ್ಕಟ್ಗಳು}other{# ಶಾರ್ಟ್ಕಟ್ಗಳು}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ಕೆಲಸ"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"ಸಂಭಾಷಣೆಗಳು"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"ಟಿಪ್ಪಣಿ ತೆಗೆದುಕೊಳ್ಳುವುದು"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"ನಿಮ್ಮ ಬೆರಳ ತುದಿಯಲ್ಲಿ ಉಪಯುಕ್ತ ಮಾಹಿತಿ"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ಆ್ಯಪ್ಗಳನ್ನು ತೆರೆಯದೆಯೇ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಲು, ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ನೀವು ವಿಜೆಟ್ಗಳನ್ನು ಸೇರಿಸಬಹುದು"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"ಸೇರಿಸಿ"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ವಿಜೆಟ್ ಸೇರಿಸಿ"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ವಿಜೆಟ್ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"ಅರ್ಥವಾಯಿತು"</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>
@@ -82,8 +84,9 @@
<string name="all_apps_button_work_label" msgid="7270707118948892488">"ಕೆಲಸದ ಅಪ್ಲಿಕೇಶನ್ಗಳ ಪಟ್ಟಿ"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"ತೆಗೆದುಹಾಕಿ"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"ಅನ್ಇನ್ಸ್ಟಾಲ್"</string>
- <string name="app_info_drop_target_label" msgid="692894985365717661">"ಅಪ್ಲಿಕೇಶನ್ ಮಾಹಿತಿ"</string>
+ <string name="app_info_drop_target_label" msgid="692894985365717661">"ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ಖಾಸಗಿಯಾಗಿ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ಆ್ಯಪ್ ಅನ್ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ಸ್ಥಾಪಿಸಿ"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ಆ್ಯಪ್ ಅನ್ನು ಸೂಚಿಸಬೇಡಿ"</string>
<string name="pin_prediction" msgid="4196423321649756498">"ಮುನ್ನೋಟ ಪಿನ್ ಮಾಡಿ"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲಾಗುತ್ತಿದೆ, <xliff:g id="PROGRESS">%2$s</xliff:g> ಪೂರ್ಣಗೊಂಡಿದೆ"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ಡೌನ್ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ, <xliff:g id="PROGRESS">%2$s</xliff:g> ಪೂರ್ಣಗೊಂಡಿದೆ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ಸ್ಥಾಪಿಸಲು ಕಾಯಲಾಗುತ್ತಿದೆ"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> ಅನ್ನು ಆರ್ಕೈವ್ ಮಾಡಲಾಗಿದೆ. ಡೌನ್ಲೋಡ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> ಅನ್ನು ಆರ್ಕೈವ್ ಮಾಡಲಾಗಿದೆ. ಡೌನ್ಲೋಡ್ ಮಾಡಲು ಮತ್ತು ಮರುಸ್ಥಾಪಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="dialog_update_title" msgid="114234265740994042">"ಆ್ಯಪ್ ಅಪ್ಡೇಟ್ ಅಗತ್ಯವಿದೆ"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"ಈ ಐಕಾನ್ಗಾಗಿ ಆ್ಯಪ್ ಅನ್ನು ಅಪ್ಡೇಟ್ ಮಾಡಲಾಗಿಲ್ಲ. ಈ ಶಾರ್ಟ್ಕಟ್ ಅನ್ನು ಮರು-ಸಕ್ರಿಯಗೊಳಿಸಲು ನೀವು ಹಸ್ತಚಾಲಿತವಾಗಿ ಅಪ್ಡೇಟ್ ಮಾಡಬಹುದು ಅಥವಾ ಐಕಾನ್ ಅನ್ನು ತೆಗೆದುಹಾಕಬಹುದು."</string>
<string name="dialog_update" msgid="2178028071796141234">"ಅಪ್ಡೇಟ್ ಮಾಡಿ"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ಫಿಲ್ಟರ್"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"ವಿಫಲವಾಗಿದೆ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"ಖಾಸಗಿ ಸ್ಪೇಸ್"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"ಖಾಸಗಿ ಆ್ಯಪ್ಗಳನ್ನು ಲಾಕ್ ಮಾಡಿ ಮತ್ತು ಮರೆಮಾಡಿ"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"ಸೆಟಪ್ ಮಾಡಲು ಅಥವಾ ತೆರೆಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ಖಾಸಗಿ"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"ಖಾಸಗಿ ಸ್ಪೇಸ್ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"ಖಾಸಗಿ ಸ್ಪೇಸ್ ಅನ್ನು ಲಾಕ್/ಅನ್ಲಾಕ್ ಮಾಡಿ"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ಖಾಸಗಿ, ಅನ್ಲಾಕ್ ಮಾಡಲಾಗಿದೆ."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ಖಾಸಗಿ, ಲಾಕ್ ಮಾಡಲಾಗಿದೆ."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ಲಾಕ್ ಮಾಡಿ"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"ಖಾಸಗಿ ಸ್ಪೇಸ್ ಪರಿವರ್ತನೆಯಾಗುತ್ತಿದೆ"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ಆ್ಯಪ್ಗಳನ್ನು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ಇನ್ಸ್ಟಾಲ್"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"ಆ್ಯಪ್ಗಳನ್ನು ಪ್ರೈವೇಟ್ ಸ್ಪೇಸ್ನಲ್ಲಿ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ಓವರ್ಫ್ಲೋ"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 5c1e854..9f64cfb 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"화면 분할"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 앱 정보"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s의 사용량 설정"</string>
<string name="save_app_pair" msgid="5647523853662686243">"앱 페어링 저장"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"이 앱 페어링은 이 기기에서 지원되지 않습니다"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> 위젯이 홈 화면에 추가됨"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"추천"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"매일 사용하는 항목"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"추천 뉴스"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"휴식 공간"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"피트니스 목표 달성"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"사전에 날씨 확인"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"좋아할 만한 항목"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"필수"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"뉴스 및 잡지"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"엔터테인먼트"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"소셜"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"추천"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"오른쪽에 <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> 위젯, 왼쪽에 검색 및 옵션"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{위젯 #개}other{위젯 #개}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{바로가기 #개}other{바로가기 #개}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"직장 위젯"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"대화"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"메모"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"빠르게 유용한 정보 확인"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"앱을 열지 않고 정보를 확인하려면 홈 화면에 위젯을 추가하세요."</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"추가"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> 위젯 추가"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"탭하여 위젯 설정 변경"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"확인"</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>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"삭제"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"제거"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"앱 정보"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"비공개 설치"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"비공개 스페이스에 설치"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"앱 제거"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"설치"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"앱 제안 받지 않음"</string>
<string name="pin_prediction" msgid="4196423321649756498">"예상 앱 고정"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> 설치 중, <xliff:g id="PROGRESS">%2$s</xliff:g> 완료"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> 다운로드 중, <xliff:g id="PROGRESS">%2$s</xliff:g> 완료"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> 설치 대기 중"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> 앱이 보관처리되었습니다. 다운로드하려면 탭하세요."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> 앱이 보관처리되었습니다. 탭하여 다운로드하고 복원하세요"</string>
<string name="dialog_update_title" msgid="114234265740994042">"앱 업데이트 필요"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"바로가기 아이콘의 앱이 업데이트되지 않았습니다. 직접 업데이트하여 앱 바로가기를 다시 사용할 수 있도록 하거나 아이콘을 삭제하세요."</string>
<string name="dialog_update" msgid="2178028071796141234">"업데이트"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"필터"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"실패: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"비공개 스페이스"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"비공개 앱을 잠그고 숨겨진 상태로 유지"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"탭하여 설정 또는 열기"</string>
<string name="ps_container_title" msgid="4391796149519594205">"비공개"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"비공개 스페이스 설정"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"비공개 스페이스 잠금/잠금 해제"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"비공개, 잠금 해제됨."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"비공개, 잠김."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"잠금"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"비공개 스페이스 전환"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"앱 설치"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"설치"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"비공개 스페이스에 앱 설치"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"오버플로"</string>
</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 4c73a20..3e67c36 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлүү"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s колдонмосу жөнүндө маалымат"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s колдонмосун пайдалануу параметрлери"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Колдонмолорду сактап коюу"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Бул эки колдонмону бул түзмөктө бир маалда пайдаланууга болбойт"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджети башкы экранга кошулду"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Сунуштар"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Күнүмдүк керектелүүчү нерселер"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Сиз үчүн жаңылыктар"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Чер жазуу"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Фитнес максаттарыңызга жетиңиз"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Аба ырайы тууралуу маалымат"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Төмөнкүлөр да жагышы мүмкүн"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Эң зарыл параметрлер"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Жаңылыктар жана журналдар"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Көңүл ачуу"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Коомдук тармактар"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Сизге сунушталат"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> виджеттери оң, ал эми издөө жана параметрлер сол жакта"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# виджет}other{# виджет}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# ыкчам баскыч}other{# ыкчам баскыч}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Жумуш"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Сүйлөшүүлөр"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Эскертме жазуу"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Керектүү маалымат манжаңыздын учунда"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Бир нерсе билүү үчүн колдонмолорду улам ачып убара болбостон, башкы экранга виджеттерди кошуп коюңуз."</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Кошуу"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджетин кошуу"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Виджеттин параметрлерин өзгөртүү үчүн таптап коюңуз"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Түшүндүм"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Чыгарып салуу"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Колдонмо тууралуу"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Жеке мейкиндикке орнотуу"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Колдонмону чыгарып салуу"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Орнотуу"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Cунушталбасын"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Божомолдонгон колдонмону кадап коюу"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> орнотулууда, <xliff:g id="PROGRESS">%2$s</xliff:g> аткарылды"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> жүктөлүп алынууда, <xliff:g id="PROGRESS">%2$s</xliff:g> аяктады"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> орнотулушу күтүлүүдө"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> архивделди. Жүктөп алуу үчүн тийип коюңуз."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> архивделди. Жүктөп алуу жана калыбына келтирүү үчүн таптаңыз."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Колдонмону жаңыртыңыз"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Бул сүрөтчөнүн колдонмосу жаңыртылган эмес. Ыкчам баскычты кайра иштетүү үчүн аны кол менен жаңыртып же сүрөтчөнү өчүрүп койсоңуз болот."</string>
<string name="dialog_update" msgid="2178028071796141234">"Жаңыртуу"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Чыпкалоо"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Аткарылган жок: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Жеке мейкиндик"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Жеке колдонмолорду кулпулап жана жашырып коюңуз"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Тууралоо же ачуу үчүн тийип коюңуз"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Жеке"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Жеке мейкиндиктин параметрлери"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Жеке мейкиндикти кулпулоо/кулпусун ачуу"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Купуя, кулпусу ачык."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Купуя, кулпуланган."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Кулпулоо"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Жеке чөйрөгө өтүү"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Колдонмолорду орнотуу"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Орнотуу"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Колдонмолорду Жеке мейкиндикке орнотуe"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Кошумча меню"</string>
</resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 0ff2d4e..e0cd3ee 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ວິດເຈັດຖືກປິດໃນ Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ບໍ່ສາມາດໃຊ້ທາງລັດໄດ້"</string>
<string name="home_screen" msgid="5629429142036709174">"ໂຮມສະກຣີນ"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"ຕັ້ງ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ເປັນແອັບໂຮມເລີ່ມຕົ້ນໃນການຕັ້ງຄ່າ"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ແບ່ງໜ້າຈໍ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"ຂໍ້ມູນແອັບສຳລັບ %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"ການຕັ້ງຄ່າການນຳໃຊ້ສຳລັບ %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ບັນທຶກຈັບຄູ່ແອັບ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ການຈັບຄູ່ແອັບນີ້ບໍ່ຮອງຮັບຢູ່ອຸປະກອນນີ້"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"ເພີ່ມວິດເຈັດ <xliff:g id="WIDGET_NAME">%1$s</xliff:g> ໃສ່ໂຮມສະກຣີນແລ້ວ"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"ການແນະນຳ"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"ສິ່ງຈຳເປັນໃນຊີວິດປະຈຳວັນຂອງທ່ານ"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"ຂ່າວສຳລັບທ່ານ"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"ພື້ນທີ່ພັກຜ່ອນຂອງທ່ານ"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"ບັນລຸເປົ້າໝາຍການອອກກຳລັງກາຍຂອງທ່ານ"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"ຮູ້ສະພາບອາກາດລ່ວງໜ້າ"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"ທ່ານອາດຈະມັກ"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ສິ່ງຈຳເປັນ"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ຂ່າວ ແລະ ວາລະສານ"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"ຄວາມບັນເທີງ"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"ສັງຄົມ"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"ແນະນຳສຳລັບທ່ານ"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"ວິດເຈັດ <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ຢູ່ທາງຂວາ, ການຊອກຫາ ແລະ ຕົວເລືອກຢູ່ທາງຊ້າຍ"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# ວິດເຈັດ}other{# ວິດເຈັດ}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# ທາງລັດ}other{# ທາງລັດ}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ວຽກ"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"ການສົນທະນາ"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"ການຈົດບັນທຶກ"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"ຂໍ້ມູນທີ່ເປັນປະໂຫຍດຢູ່ປາຍນິ້ວຂອງທ່ານ"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ເພື່ອຮັບຂໍ້ມູນໂດຍບໍ່ຕ້ອງເປີດແອັບ, ທ່ານສາມາດເພີ່ມວິດເຈັດໃສ່ໂຮມສະກຣີນຂອງທ່ານໄດ້"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"ເພີ່ມ"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"ເພີ່ມວິດເຈັດ <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ແຕະເພື່ອປ່ຽນການຕັ້ງຄ່າວິດເຈັດ"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"ເຂົ້າໃຈແລ້ວ"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"ຖອນການຕິດຕັ້ງ"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ຂໍ້ມູນແອັບ"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ຕິດຕັ້ງໃນສ່ວນຕົວ"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ຖອນການຕິດຕັ້ງແອັບ"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ຕິດຕັ້ງ"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ຢ່າແນະນຳແອັບ"</string>
<string name="pin_prediction" msgid="4196423321649756498">"ປັກໝຸດການຄາດເດົາ"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"ກຳລັງຕິດຕັ້ງ <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> ສຳເລັດແລ້ວ"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ກຳລັງດາວໂຫຼດ, <xliff:g id="PROGRESS">%2$s</xliff:g> ສຳເລັດ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ກຳລັງລໍຖ້າຕິດຕັ້ງ"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> ຖືກເກັບໄວ້ໃນແຟ້ມ. ແຕະເພື່ອດາວໂຫລດ."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> ຖືກເກັບໄວ້ໃນແຟ້ມ. ແຕະເພື່ອດາວໂຫຼດ ແລະ ກູ້ຄືນ."</string>
<string name="dialog_update_title" msgid="114234265740994042">"ຈຳເປັນຕ້ອງອັບເດດແອັບ"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"ບໍ່ໄດ້ອັບເດດແອັບສຳລັບໄອຄອນນີ້. ທ່ານສາມາດອັບເດດເອງໄດ້ເພື່ອເປີດການນຳໃຊ້ທາງລັດນີ້ຄືນໃໝ່ ຫຼື ລຶບໄອຄອນດັ່ງກ່າວອອກ."</string>
<string name="dialog_update" msgid="2178028071796141234">"ອັບເດດ"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ກັ່ນຕອງ"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"ບໍ່ສຳເລັດ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"ພື້ນທີ່ສ່ວນຕົວ"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"ລັອກ ແລະ ເຊື່ອງແອັບສ່ວນຕົວໄວ້"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"ແຕະເພື່ອຕັ້ງຄ່າ ຫຼື ເປີດ"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ສ່ວນຕົວ"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"ການຕັ້ງຄ່າພື້ນທີ່ສ່ວນຕົວ"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"ລັອກ/ປົດລັອກພື້ນທີ່ສ່ວນຕົວ"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ສ່ວນຕົວ, ປົດລັອກແລ້ວ."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ສ່ວນຕົວ, ລັອກແລ້ວ."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ລັອກ"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"ການປ່ຽນແປງພື້ນທີ່ສ່ວນຕົວ"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ຕິດຕັ້ງແອັບ"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ຕິດຕັ້ງ"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"ຕິດຕັ້ງແອັບໄປໃສ່ພື້ນທີ່ສ່ວນບຸກຄົນ"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ການດຳເນີນການເພີ່ມເຕີມ"</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index ef81b96..9846525 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Valdikliai išjungti Saugiame režime"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Sparčiojo klavišo negalima naudoti"</string>
<string name="home_screen" msgid="5629429142036709174">"Pagrindinis"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nustatykite „<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>“ kaip numatytąją pagrindinę programą skiltyje „Nustatymai“"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidyto ekrano režimas"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Programos „%1$s“ informacija"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"„%1$s“ naudojimo nustatymai"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Išsaugoti programų porą"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ši programų pora šiame įrenginyje nepalaikoma"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Atlenkite įrenginį, kad galėtumėte naudoti šią programų porą."</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Programų pora nepasiekiama"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Dukart pal. ir palaik., kad perkeltumėte valdiklį."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dukart palieskite ir palaikykite, kad perkeltumėte valdiklį ar naudotumėte tinkintus veiksmus."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d plotis ir %2$d aukštis"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> valdiklis"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Valdiklis: <xliff:g id="WIDGET_NAME">%1$s</xliff:g>; %2$d pločio ir %3$d aukščio"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Paliesdami ir palaikydami valdiklį galite judėti pagrindiniame ekrane"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Pridėti prie pagrindinio ekrano"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Valdiklis „<xliff:g id="WIDGET_NAME">%1$s</xliff:g>“ pridėtas prie pagrindinio ekrano"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Pasiūlymai"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Tai, ko jums reikia kasdien"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Naujienos jums"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Jūsų atsipalaidavimo zona"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Pasiekite mankštos tikslus"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Visada žinokite, kokie bus orai"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Jums taip pat gali patikti"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Būtiniausi"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Naujienos ir žurnalai"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Pramogos"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Socialiniai tinklai"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Siūloma jums"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> valdikliai dešinėje, paieška ir parinktys kairėje"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# valdiklis}one{# valdiklis}few{# valdikliai}many{# valdiklio}other{# valdiklių}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# spartusis klavišas}one{# spartusis klavišas}few{# spartieji klavišai}many{# sparčiojo klavišo}other{# sparčiųjų klavišų}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Darbas"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Pokalbiai"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Užrašų kūrimas"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Lengvai pasiekiama naudinga informacija"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Jei norite gauti informacijos neatidarę programų, galite pridėti valdiklių pagrindiniame ekrane"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Pridėti"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Pridėti valdiklį: <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Palieskite, kad pakeistumėte valdiklio nustatymus"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Supratau"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Pakeisti valdiklio nustatymus"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Paieškos programos"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Įkeliamos programos…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Pašalinti"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Programos inform."</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Įdiegti privačiai"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Pašalinti programą"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Įdiegti"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Nesiūlyti programos"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Prisegti numatymą"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Įdiegiama: „<xliff:g id="NAME">%1$s</xliff:g>“; baigta: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Atsisiunčiama programa „<xliff:g id="NAME">%1$s</xliff:g>“, <xliff:g id="PROGRESS">%2$s</xliff:g> baigta"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Laukiama, kol bus įdiegta programa „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"„<xliff:g id="NAME">%1$s</xliff:g>“ suarchyvuota. Palieskite, kad atsisiųstumėte."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Programa „<xliff:g id="NAME">%1$s</xliff:g>“ suarchyvuota. Palieskite, jei norite atsisiųsti ir atkurti."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Būtina atnaujinti programą"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Šios piktogramos programa neatnaujinta. Galite patys atnaujinti, kad iš naujo įgalintumėte šį spartųjį klavišą, arba pašalinkite piktogramą."</string>
<string name="dialog_update" msgid="2178028071796141234">"Atnaujinti"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtruoti"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Nepavyko: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privati erdvė"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Privačių programų užrakinimas ir slėpimas"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Palieskite, kad nustatytumėte arba atidarytumėte"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privatus"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Privačios erdvės nustatymai"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Užrakinti ir (arba) atrakinti privačią erdvę"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privatus, atrakintas."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privatus, užrakintas."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Užrakinti"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Privačios erdvės perkėlimas"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Programų diegimas"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Įdiegti"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Įdiegti programas privačioje erdvėje"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Perpildymas"</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index d04afa1..49f7ffe 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Logrīki atspējoti drošajā režīmā"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Saīsne nav pieejama."</string>
<string name="home_screen" msgid="5629429142036709174">"Sākums"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Iestatiet <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kā noklusējuma sākuma lietotni iestatījumos."</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Sadalīt ekrānu"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s: informācija par lietotni"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Lietojuma iestatījumi: %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Saglabāt lietotņu pāri"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Šis lietotņu pāris netiek atbalstīts šajā ierīcē"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Atveriet ierīci, lai izmantotu šo lietotņu pāri"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Lietotņu pāris nav pieejams"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Lai pārvietotu logrīku, pieskarieties un turiet."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Lai pārvietotu logrīku, uz tā veiciet dubultskārienu un turiet. Varat arī veikt pielāgotas darbības."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d plats un %2$d augsts"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Logrīks <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Logrīks <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d plats un %3$d augsts"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Pieskarieties logrīkam un turiet to, lai to pārvietotu pa sākuma ekrānu."</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Pievienot sākuma ekrānam"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Logrīks “<xliff:g id="WIDGET_NAME">%1$s</xliff:g>” ir pievienots sākuma ekrānam"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Ieteikumi"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Jums ikdienā vajadzīgais"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Ziņas jums"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Jūsu atpūtas stūrītis"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Sasniedziet fitnesa mērķus"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Neļaujiet laikapstākļiem jūs pārsteigt"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Jums varētu patikt arī…"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Produktivitātei"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Ziņas un žurnāli"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Izklaide"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sociālie tīkli"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Ieteikumi jums"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Pa labi logrīki <xliff:g id="SELECTED_HEADER">%1$s</xliff:g>, pa kreisi meklēšana un iespējas"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# logrīks}zero{# logrīku}one{# logrīks}other{# logrīki}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# saīsne}zero{# saīšņu}one{# saīsne}other{# saīsnes}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Darba"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Sarunas"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Piezīmju pierakstīšana"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Ērta piekļuve noderīgai informācijai"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Lai iegūtu informāciju, neatverot lietotnes, varat pievienot sākuma ekrānam logrīkus"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Pievienot"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Pievienot logrīku <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Pieskarieties, lai mainītu logrīka iestatījumus."</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Labi"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Mainīt logrīka iestatījumus"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Meklēt lietotnes"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Notiek lietotņu ielāde…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Atinstalēt"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Par lietotni"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instalēt privāti"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Atinstalēt lietotni"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instalēt"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Neieteikt lietotni"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Piespraust prognozēto lietotni"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Notiek lietotnes “<xliff:g id="NAME">%1$s</xliff:g>” instalēšana. Norise: <xliff:g id="PROGRESS">%2$s</xliff:g>."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Lietotnes <xliff:g id="NAME">%1$s</xliff:g> lejupielāde (<xliff:g id="PROGRESS">%2$s</xliff:g> pabeigti)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Notiek <xliff:g id="NAME">%1$s</xliff:g> instalēšana"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Lietotne <xliff:g id="NAME">%1$s</xliff:g> ir arhivēta. Pieskarieties, lai lejupielādētu."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Lietotne <xliff:g id="NAME">%1$s</xliff:g> ir arhivēta; lai lejupielādētu un atjaunotu, pieskarieties"</string>
<string name="dialog_update_title" msgid="114234265740994042">"Lietotne ir jāatjaunina"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Šai ikonai paredzētā lietotne nav atjaunināta. Varat to atjaunināt manuāli, lai atkārtoti iespējotu šo saīsni, vai noņemt ikonu."</string>
<string name="dialog_update" msgid="2178028071796141234">"Atjaunināt"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrs"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Neizdevās: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privātā telpa"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Paslēpiet privātās lietotnes un bloķējiet piekļuvi tām"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Pieskarieties, lai iestatītu vai atvērtu"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privātā mape"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Privātās mapes iestatījumi"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloķēt/atbloķēt privāto mapi"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privāta un nav bloķēta."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privāta un bloķēta."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Bloķēšana"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Pāriet uz privāto mapi"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Lietotņu instalēšana"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalēt"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalējiet lietotnes privātajā telpā."</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Pārpilde"</string>
</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 6a4dd26..2bfa089 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Поделен екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Податоци за апликација за %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Поставки за користење за %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Зачувај го парот апликации"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Паров апликации не е поддржан на уредов"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Виџетот <xliff:g id="WIDGET_NAME">%1$s</xliff:g> е додаден на почетниот екран"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Предлози"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Вашите секојдневни неопходности"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Вести за вас"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Вашата зона за релаксација"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Достигнете ги целите за фитнес"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Бидете во тек со временската прогноза"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Можеби ќе ви се допадне и"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Неопходни"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Вести и списанија"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Забава"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Друштвени"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Препорачано за вас"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> виџети оддесно, „Пребарување“ и „Опции“ одлево"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# виџет}one{# виџет}other{# виџети}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# кратенка}one{# кратенка}other{# кратенки}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Работни"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Разговори"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Фаќање белешки"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Корисни информации на дофат на прстите"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"За да добивате информации без да ги отворате апликациите, може да додадете виџети на почетниот екран"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Додај"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Додај го виџетот <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Допрете за да ги промените поставките за виџетот"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Сфатив"</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>
@@ -84,8 +86,9 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Деинсталирај"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Инф. за апликација"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Инстал. во приватен"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Деинсталирај ја апликацијата"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Инсталирај"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Не предлагај апликација"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Не предлагај апл."</string>
<string name="pin_prediction" msgid="4196423321649756498">"Закачи го предвидувањето"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"инсталирање кратенки"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Овозможува апликацијата да додава кратенки без интервенција на корисникот."</string>
@@ -124,7 +127,7 @@
<string name="msg_missing_notification_access" msgid="281113995110910548">"За да се прикажуваат „Точки за известување“, вклучете ги известувањата за апликацијата <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="title_change_settings" msgid="1376365968844349552">"Промени ги поставките"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Прикажувај точки за известување"</string>
- <string name="developer_options_title" msgid="700788437593726194">"Опции за програмери"</string>
+ <string name="developer_options_title" msgid="700788437593726194">"Програмерски опции"</string>
<string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Додавај икони за апликации на почетниот екран"</string>
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"За нови апликации"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"Непознато"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> се инсталира, <xliff:g id="PROGRESS">%2$s</xliff:g> завршено"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Се презема <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> завршено"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> чека да се инсталира"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> е архивирана. Допрете за преземање."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Апликацијата <xliff:g id="NAME">%1$s</xliff:g> е архивирана. Допрете за да преземете и вратите."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Потребно е ажурирање на апликацијата"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Апликацијата за оваа икона не е ажурирана. Може да ажурирате рачно за да повторно се овозможи кратенкава или отстранете ја иконата."</string>
<string name="dialog_update" msgid="2178028071796141234">"Ажурирај"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Филтер"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Не успеа: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Приватен простор"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Чувајте ги приватните апликации заклучени и скриени"</string>
- <string name="ps_container_title" msgid="4391796149519594205">"Приватен"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Допрете за да поставите или отворите"</string>
+ <string name="ps_container_title" msgid="4391796149519594205">"Приватен простор"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Поставки за „Приватен простор“"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Заклучување/отклучување на „Приватен простор“"</string>
- <string name="ps_container_lock_title" msgid="2640257399982364682">"Брава"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Приватно, отклучено."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Приватно, заклучено."</string>
+ <string name="ps_container_lock_title" msgid="2640257399982364682">"Заклучи"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Префрлање на „Приватен простор“"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Инсталирајте апликации"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Инсталирајте"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Инсталирање апликации во „Приватен простор“"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Проширено балонче"</string>
</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 3952b22..a2babd5 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"സ്ക്രീൻ വിഭജന മോഡ്"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s എന്നതിന്റെ ആപ്പ് വിവരങ്ങൾ"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s എന്നതിനുള്ള ഉപയോഗ ക്രമീകരണം"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ആപ്പ് ജോടി സംരക്ഷിക്കുക"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ഈ ഉപകരണത്തിൽ ഈ ആപ്പ് ജോടിക്ക് പിന്തുണയില്ല"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> വിജറ്റ് ഹോം സ്ക്രീനിലേക്ക് ചേർത്തു"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"നിർദ്ദേശങ്ങൾ"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"ഓരോ ദിവസവും ആവശ്യമായവ"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"നിങ്ങൾക്കായുള്ള വാർത്ത"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"നിങ്ങൾക്ക് സുഖപ്രദമായ സ്ഥലം"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"ശാരീരികക്ഷമതയുമായി ബന്ധപ്പെട്ട ലക്ഷ്യങ്ങൾ കൈവരിക്കൂ"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"കാലാവസ്ഥ മുൻകൂട്ടി മനസ്സിലാക്കുക"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"നിങ്ങൾക്ക് ഇനിപ്പറയുന്നവ ഇഷ്ടമായേക്കാം"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ആവശ്യമായവ"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"വാർത്തകളും മാസികകളും"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"വിനോദം"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"സാമൂഹികം"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"നിങ്ങൾക്കായി നിർദ്ദേശിച്ചവ"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"വലതുവശത്ത് <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> വിജറ്റുകളും ഇടതുവശത്ത് തിരയൽ, ഓപ്ഷനുകൾ എന്നിവയും"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# വിജറ്റ്}other{# വിജറ്റുകൾ}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# കുറുക്കുവഴി}other{# കുറുക്കുവഴികൾ}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ജോലി"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"സംഭാഷണങ്ങൾ"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"കുറിപ്പ് രേഖപ്പെടുത്തൽ"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"ഉപകാരപ്രദമായ വിവരങ്ങൾ നിങ്ങളുടെ വിരൽത്തുമ്പിൽ"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ആപ്പുകൾ തുറക്കാതെ വിവരങ്ങൾ ലഭിക്കാൻ, നിങ്ങൾക്ക് ഹോം സ്ക്രീനിലേക്ക് വിജറ്റുകൾ ചേർക്കാം"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"ചേർക്കുക"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> വിജറ്റ് ചേർക്കുക"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"വിജറ്റ് ക്രമീകരണം മാറ്റാൻ ടാപ്പ് ചെയ്യുക"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"മനസ്സിലായി"</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>
@@ -84,8 +86,9 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"അൺഇൻസ്റ്റാൾ"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ആപ്പ് വിവരങ്ങൾ"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"സ്വകാര്യമായി ഇൻസ്റ്റാൾ ചെയ്യൂ"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ആപ്പ് അൺഇൻസ്റ്റാൾ ചെയ്യുക"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ഇൻസ്റ്റാൾ ചെയ്യുക"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"ആപ്പ് നിർദ്ദേശിക്കരുത്"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"ആപ്പ് നിർദ്ദേശിക്കേണ്ട"</string>
<string name="pin_prediction" msgid="4196423321649756498">"പ്രവചനം പിൻ ചെയ്യുക"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"കുറുക്കുവഴികൾ ഇൻസ്റ്റാളുചെയ്യുക"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"ഉപയോക്തൃ ഇടപെടൽ ഇല്ലാതെ കുറുക്കുവഴികൾ ചേർക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ഇൻസ്റ്റാൾ ചെയ്യുന്നു, <xliff:g id="PROGRESS">%2$s</xliff:g> പൂർത്തിയായി"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ഡൗൺലോഡ് ചെയ്യുന്നു, <xliff:g id="PROGRESS">%2$s</xliff:g> പൂർത്തിയായി"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"ഇൻസ്റ്റാൾ ചെയ്യാൻ <xliff:g id="NAME">%1$s</xliff:g> കാക്കുന്നു"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> ആർക്കൈവ് ചെയ്തു. ഡൗൺലോഡ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> ആർക്കൈവ് ചെയ്തു. ഡൗൺലോഡ് ചെയ്യാനും പുനഃസ്ഥാപിക്കാനും ടാപ്പ് ചെയ്യുക."</string>
<string name="dialog_update_title" msgid="114234265740994042">"ആപ്പ് അപ്ഡേറ്റ് ചെയ്യേണ്ടതുണ്ട്"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"ഈ ഐക്കണിനുള്ള ആപ്പ് അപ്ഡേറ്റ് ചെയ്തിട്ടില്ല. ഈ കുറുക്കുവഴി വീണ്ടും പ്രവർത്തനക്ഷമമാക്കാൻ നിങ്ങൾക്ക് നേരിട്ട് അപ്ഡേറ്റ് ചെയ്യാം അല്ലെങ്കിൽ ഐക്കൺ നീക്കം ചെയ്യാം."</string>
<string name="dialog_update" msgid="2178028071796141234">"അപ്ഡേറ്റ് ചെയ്യുക"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ഫിൽട്ടർ ചെയ്യുക"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"പരാജയപ്പെട്ടു: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"സ്വകാര്യ സ്പേസ്"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"സ്വകാര്യ ആപ്പുകൾ ലോക്ക് ചെയ്ത് മറയ്ക്കുക"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"സജ്ജീകരിക്കാനോ തുറക്കാനോ ടാപ്പ് ചെയ്യുക"</string>
<string name="ps_container_title" msgid="4391796149519594205">"സ്വകാര്യം"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"സ്വകാര്യ സ്പേസ് ക്രമീകരണം"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"സ്വകാര്യ സ്പേസ് ലോക്ക് ചെയ്യുക/അൺലോക്ക് ചെയ്യുക"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"സ്വകാര്യം, അൺലോക്ക് ചെയ്തിരിക്കുന്നു."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"സ്വകാര്യം, ലോക്ക് ചെയ്തു."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ലോക്ക് ചെയ്യുക"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"പ്രൈവറ്റ് സ്പേസ് ട്രാൻസിഷനിംഗ്"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യുക"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ഇൻസ്റ്റാൾ ചെയ്യുക"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"സ്വകാര്യ സ്പേസിലേക്ക് ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യുക"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ഓവർഫ്ലോ"</string>
</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 7a73041..93a1c9c 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Safe горимд виджетүүдийг идэвхгүйжүүлсэн"</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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Дэлгэцийг хуваах"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-н аппын мэдээлэл"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s-н ашиглалтын тохиргоо"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Апп хослуулалтыг хадгалах"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Энэ апп хослуулалтыг уг төхөөрөмж дээр дэмждэггүй"</string>
<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="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_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджет, %3$d-н өндрийг харьцах нь %2$d-н өргөн"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Виджетийг үндсэн нүүрний эргэн тойронд зөөхийн тулд түүнд хүрээд, удаан дарна уу"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Үндсэн нүүрэнд нэмэх"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджетийг үндсэн нүүрэнд нэмсэн"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Зөвлөмжүүд"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Таны өдөр тутмын хэрэгцээт зүйлс"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Танд зориулсан мэдээ"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Таны амралтын бүс"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Фитнесийн зорилгодоо хүрээрэй"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Цаг агаарын урьдчилсан мэдээлэлтэй байгаарай"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Танд таалагдаж магадгүй"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Зайлшгүй хэрэгтэй"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Мэдээ, сэтгүүлүүд"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Энтертэйнмент"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Сошиал"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Танд санал болгосон"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Баруун талд <xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-н виджет, зүүн талд хайлт болон сонгуултууд байна"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# виджет}other{# виджет}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# товчлол}other{# товчлол}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Ажил"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Харилцан яриа"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Тэмдэглэл хөтлөх"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Хэрэгтэй мэдээллээ хурууныхаа үзүүрээр аваарай"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Аппуудыг нээлгүйгээр мэдээлэл авахын тулд та үндсэн нүүрэндээ виджетүүд нэмэх боломжтой"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Нэмэх"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджетийг нэмэх"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Жижиг хэрэгслийн тохиргоог өөрчлөхийн тулд товшино уу"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Ойлголоо"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Устгах"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Аппын мэдээлэл"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Хувийнхад суулгах"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Аппыг устгах"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Суулгах"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Апп бүү санал болго"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Таамаглалыг бэхлэх"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g>-г суулгаж байна. <xliff:g id="PROGRESS">%2$s</xliff:g> дууссан"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>-г татаж байна, <xliff:g id="PROGRESS">%2$s</xliff:g> татсан"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> нь суулгахыг хүлээж байна"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g>-г архивласан. Татахын тулд товшино уу."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g>-г архивласан. Татаж, сэргээхийн тулд товшино уу."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Аппын шинэчлэлт шаардлагатай"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Энэ дүрс тэмдгийн аппыг шинэчлээгүй. Та энэ товчлолыг дахин идэвхжүүлэх эсвэл дүрсийг хасахын тулд гараар шинэчлэх боломжтой."</string>
<string name="dialog_update" msgid="2178028071796141234">"Шинэчлэх"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Шүүлтүүр"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Амжилтгүй болсон: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Хувийн орон зай"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Хувийн аппуудыг түгжээтэй бөгөөд нуугдсан байлгана уу"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Тохируулах эсвэл нээхийн тулд товших"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Хувийн"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Private Space-н тохиргоо"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Private Space-г түгжих/түгжээг тайлах"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Хувийн, түгжээг тайлсан."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Хувийн, түгжээтэй."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Түгжээ"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Private Space-н шилжилт"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Аппуудыг суулгах"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Суулгах"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Хувийн орон зайд аппууд суулгана уу"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Урт цэс"</string>
</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index bf83246..c084fab 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s साठी ॲपशी संबंधित माहिती"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s साठी वापरासंबंधित सेटिंग्ज"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ॲपची जोडी सेव्ह करा"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"या ॲपची जोडीला या डिव्हाइसवर सपोर्ट नाही"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> हे विजेट तुमच्या होम स्क्रीनवर जोडले आहे"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"सूचना"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"तुमच्या दररोजच्या आवश्यक गोष्टी"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"तुमच्यासाठी बातम्या"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"तुमचा आरामदायक झोन"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"तुमची फिटनेस ध्येये गाठा"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"हवामानासंबंधित बातम्या आगामी मिळवा"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"तुम्हाला हेदेखील आवडू शकते"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"आवश्यक गोष्टी"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"बातम्या आणि मासिके"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"मनोरंजन"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"सोशल"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"तुमच्यासाठी सुचवलेले"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"उजवीकडे <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> विजेट, डावीकडे शोध आणि पर्याय"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# विजेट}other{# विजेट}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# शॉर्टकट}other{# शॉर्टकट}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ऑफिस"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"संभाषणे"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"टिपा घेणे"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"तुमच्यासाठी सहज उपलब्ध असलेली माहिती"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ॲप्स न उघडता माहिती मिळवण्यासाठी, तुम्ही तुमच्या होम स्क्रीनवर विजेट जोडू शकता"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"जोडा"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट जोडा"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"विजेट सेटिंग्ज बदलण्यासाठी टॅप करा"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"समजले"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"अनइंस्टॉल करा"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"अॅप माहिती"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"खाजगीत इंस्टॉल करा"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"अॅप अनइंस्टॉल करा"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"इंस्टॉल करा"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ॲप सुचवू नका"</string>
<string name="pin_prediction" msgid="4196423321649756498">"पूर्वानुमान पिन करा"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल करत आहे, <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड होत आहे , <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल करण्याची प्रतिक्षा करत आहे"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> संग्रहित केले आहे. डाउनलोड करण्यासाठी टॅप करा."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> संग्रहित केले आहे. डाउनलोड करून रिस्टोअर करण्यासाठी टॅप करा."</string>
<string name="dialog_update_title" msgid="114234265740994042">"अॅप अपडेट करणे आवश्यक आहे"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"या आयकनसाठी अॅप अपडेट केलेले नाही. हा शॉटकर्ट पुन्हा सुरू करण्यासाठी तुम्ही मॅन्युअली अपडेट करू शकता किंवा आयकन काढून टाका."</string>
<string name="dialog_update" msgid="2178028071796141234">"अपडेट करा"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"फिल्टर"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"हे करता आले नाही: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"खाजगी स्पेस"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"खाजगी अॅप्स लॉक करून आणि लपवून ठेवा"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"सेट करण्यासाठी किंवा उघडण्यासाठी टॅप करा"</string>
<string name="ps_container_title" msgid="4391796149519594205">"खाजगी"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"खाजगी स्पेस ची सेटिंग्ज"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"खाजगी स्पेस लॉक/अनलॉक करा"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"खाजगी, अनलॉक केलेली."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"खाजगी, लॉक केलेली."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"लॉक"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"खाजगी स्पेस वर स्विच करणे"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"अॅप्स इंस्टॉल करा"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"इंस्टॉल करा"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"अॅप्स खाजगी स्पेस मध्ये इंस्टॉल करा"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ओव्हरफ्लो"</string>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 0f3d20d..72d92e2 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget dilumpuhkan dalam mod Selamat"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
<string name="home_screen" msgid="5629429142036709174">"Rumah"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Tetapkan <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> sebagai apl skrin utama lalai dalam Tetapan"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skrin pisah"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Maklumat apl untuk %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Tetapan penggunaan sebanyak %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Simpan gandingan apl"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Gandingan apl ini tidak disokong pada peranti ini"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Buka lipatan peranti untuk menggunakan gandingan apl ini"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Gandingan apl tidak tersedia"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Sentuh & tahan untuk menggerakkan widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Ketik dua kali & tahan untuk menggerakkan widget atau menggunakan tindakan tersuai."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Lebar %1$d kali tinggi %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, lebar %2$d kali tinggi %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Sentuh & tahan widget untuk menggerakkan widget di sekitar skrin utama"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Tambahkan pada skrin utama"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> ditambahkan pada skrin utama"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Cadangan"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Keperluan Harian Anda"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Berita Untuk Anda"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Zon Santai Anda"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Capai Matlamat Kecergasan Anda"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Ketahui Perkembangan Terkini Cuaca"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Anda Mungkin Turut Menyukai"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Penting"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Berita & majalah"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Hiburan"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sosial"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Dicadangkan untuk anda"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widget <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> pada sebelah kanan, carian dan pilihan pada sebelah kiri"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widget}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# pintasan}other{# pintasan}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Tempat kerja"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Perbualan"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Pengambilan nota"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Maklumat berguna di hujung jari anda"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Untuk mendapatkan maklumat tanpa membuka apl, anda boleh menambahkan widget pada skrin utama anda"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Tambah"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Tambahkan widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Ketik untuk menukar tetapan widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Tukar tetapan widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Cari apl"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Memuatkan apl…"</string>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Alih keluar"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Nyahpasang"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Maklumat apl"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Pasang dalam peribadi"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Pasang dalam persendirian"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Nyahpasang apl"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Pasang"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Jangan cadangkan apl"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Sematkan Ramalan"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> dipasang, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> memuat turun, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> menunggu untuk dipasang"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> diarkibkan. Ketik untuk muat turun."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> diarkibkan. Ketik untuk memuat turun dan memulihkan apl tersebut."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Kemas kini apl diperlukan"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Apl untuk ikon ini tidak dikemas kini. Anda boleh mengemas kini secara manual untuk mendayakan semula pintasan atau mengalih keluar ikon."</string>
<string name="dialog_update" msgid="2178028071796141234">"Kemas kini"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Tapis"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Gagal: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Ruang privasi"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Kunci dan sembunyikan apl peribadi"</string>
- <string name="ps_container_title" msgid="4391796149519594205">"Peribadi"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Ketik untuk menyediakan atau membuka"</string>
+ <string name="ps_container_title" msgid="4391796149519594205">"Persendirian"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Tetapan Ruang Peribadi"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Kunci/Buka kunci Ruang Peribadi"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Peribadi, tidak berkunci."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Peribadi, dikunci."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Kunci"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Peralihan Ruang Peribadi"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Pasang apl"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Pasang"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Pasang apl pada Ruang Peribadi"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Limpahan"</string>
</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 05736a5..194ece7 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s အတွက် အက်ပ်အချက်အလက်"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s အတွက် အသုံးပြုမှုဆက်တင်များ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"အက်ပ်တွဲချိတ်ခြင်း သိမ်းရန်"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ဤအက်ပ်တွဲချိတ်ခြင်းကို ဤစက်တွင် ပံ့ပိုးမထားပါ"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ဝိဂျက်ကို ပင်မစာမျက်နှာတွင် ထည့်လိုက်ပြီ"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"အကြံပြုချက်"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"သင်၏ နေ့စဉ်မရှိမဖြစ်များ"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"သင့်အတွက် သတင်းများ"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"သင်အနားယူသောနေရာ"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"သင့်ကြံ့ခိုင်ရေးပန်းတိုင်ဆီ သွားရန်"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"မိုးလေဝသကို ကြိုတင်ကာကွယ်ရန်"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"သင်နှစ်သက်နိုင်သောအရာများ"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"မရှိမဖြစ်များ"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"သတင်းနှင့် မဂ္ဂဇင်း"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"ဖျော်ဖြေရေး"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"လူမှုရေး"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"သင့်အတွက် အကြံပြုထားသည်များ"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ဝိဂျက်များသည် ညာဘက်တွင်ရှိပြီး ရှာဖွေမှုနှင့် ရွေးစရာများသည် ဘယ်ဘက်တွင်ရှိသည်"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{ဝိဂျက် # ခု}other{ဝိဂျက် # ခု}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{ဖြတ်လမ်းလင့်ခ် # ခု}other{ဖြတ်လမ်းလင့်ခ် # ခု}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"အလုပ်"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"စကားဝိုင်းများ"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"မှတ်စုလိုက်ခြင်း"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"အသုံးဝင်သော အချက်အလက်များကို အလွယ်တကူ ရယူလိုက်ပါ"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"အက်ပ်မဖွင့်ဘဲ အချက်အလက်များရယူရန် ပင်မစာမျက်နှာတွင် ဝိဂျက်များ ထည့်နိုင်သည်"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"ထည့်ရန်"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ဝိဂျက်ထည့်ရန်"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ဝိဂျက် ဆက်တင်များကို ပြောင်းရန် တို့ပါ"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"နားလည်ပြီ"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"ဖယ်ရှားရန်"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"အက်ပ်အချက်အလက်"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"သီးသန့်တွင် ထည့်သွင်းရန်"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"အက်ပ်ကို ဖယ်ရှားရန်"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ထည့်သွင်းရန်"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"အက်ပ်အကြံမပြုပါနှင့်"</string>
<string name="pin_prediction" msgid="4196423321649756498">"ခန့်မှန်းချက်ကို ပင်ထိုးရန်"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ကို ထည့်သွင်းနေသည်၊ <xliff:g id="PROGRESS">%2$s</xliff:g> ပြီးပါပြီ"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ဒေါင်းလုဒ်လုပ်နေသည်၊ <xliff:g id="PROGRESS">%2$s</xliff:g> ပြီးပါပြီ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ကိုထည့်သွင်းရန်စောင့်နေသည်"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> ကို သိမ်းထားသည်။ ဒေါင်းလုဒ်လုပ်ရန် တို့ပါ။"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> ကို သိမ်းထားသည်။ ဒေါင်းလုဒ်လုပ်ပြီး ပြန်ယူရန် တို့ပါ။"</string>
<string name="dialog_update_title" msgid="114234265740994042">"အက်ပ်ကို အပ်ဒိတ်လုပ်ရန် လိုအပ်သည်"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"ဤသင်္ကေတအတွက် အက်ပ်ကို အပ်ဒိတ်လုပ်မထားပါ။ ဤဖြတ်လမ်းလင့်ခ်ကို ပြန်ဖွင့်ရန် ကိုယ်တိုင်အပ်ဒိတ်လုပ်နိုင်သည် (သို့) သင်္ကေတကို ဖယ်ရှားနိုင်သည်။"</string>
<string name="dialog_update" msgid="2178028071796141234">"အပ်ဒိတ်လုပ်ရန်"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"စစ်ထုတ်ရန်"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"မအောင်မြင်ပါ− <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"သီးသန့်ချတ်ခန်း"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"သီးသန့်အက်ပ်များကို လော့ခ်ချပြီး ဖျောက်ထားပါ"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"စနစ်ထည့်သွင်းရန် (သို့) ဖွင့်ရန် တို့ပါ"</string>
<string name="ps_container_title" msgid="4391796149519594205">"သီးသန့်"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"သီးသန့်ချတ်ခန်း ဆက်တင်များ"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"သီးသန့်ချတ်ခန်း လော့ခ်ချ/ဖွင့်ရန်"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"သီးသန့် လော့ခ်ဖွင့်ထားသည်။"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"သီးသန့် လော့ခ်ချထားသည်။"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"လော့ခ်ချခြင်း"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"သီးသန့်ချတ်ခန်း အပြောင်းအလဲ"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"အက်ပ်များ ထည့်သွင်းခြင်း"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ထည့်သွင်းရန်"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"‘သီးသန့်နေရာ’ တွင် အက်ပ်များ ထည့်သွင်းနိုင်သည်"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"မီနူးအပို"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 72ac454..a9e6c5d 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Moduler er deaktivert i sikker modus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Snarveien er ikke tilgjengelig"</string>
<string name="home_screen" msgid="5629429142036709174">"Startskjerm"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Angi <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> som standard startsideapp i innstillingene"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delt skjerm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformasjon for %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Bruksinnstillinger for %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Lagre apptilkoblingen"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Denne apptilkoblingen støttes ikke på denne enheten"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Åpne enheten for å bruke denne apptilkoblingen"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Apptilkoblingen er ikke tilgjengelig"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Trykk og hold for å flytte en modul."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dobbelttrykk og hold inne for å flytte en modul eller bruke tilpassede handlinger."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d bredde x %2$d høyde"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>-modul"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>-modulen, %2$d bred og %3$d høy"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Trykk og hold på modulen for å bevege den rundt på startskjermen"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Legg til på startskjermen"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>-modulen er lagt til på startskjermen"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Forslag"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Viktige apper for dagliglivet"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Nyheter for deg"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Avslappingssonen din"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Nå treningsmålene dine"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Hold deg i forkant av været"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Kanskje du også liker"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Anbefalt"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Nyheter og tidsskrifter"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Underholdning"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sosialt"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Foreslått for deg"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> moduler til høyre, søk og alternativer til venstre"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# modul}other{# moduler}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# snarvei}other{# snarveier}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Jobb"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Samtaler"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Notatskriving"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Lett tilgjengelig nyttig informasjon"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"For å se informasjon uten å åpne apper kan du legge til moduler på startskjermen"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Legg til"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Legg til <xliff:g id="WIDGET_NAME">%1$s</xliff:g>-modulen"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Trykk for å endre modulinnstillinger"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Greit"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Endre modulinnstillinger"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Søk etter apper"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Laster inn appene …"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Avinstaller"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Info om appen"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Installer privat"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Avinstaller appen"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Installer"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Ikke foreslå app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Fest forslaget"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installerer, <xliff:g id="PROGRESS">%2$s</xliff:g> er fullført"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Laster ned <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> er fullført"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Venter på å installere <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> er arkivert. Trykk for å laste den ned."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> er arkivert. Trykk for å laste ned og gjenopprette."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Appen må oppdateres"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Appen for dette ikonet er ikke oppdatert. Du kan oppdatere manuelt for å aktivere denne snarveien igjen, eller du kan fjerne ikonet."</string>
<string name="dialog_update" msgid="2178028071796141234">"Oppdater"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Mislyktes: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privat område"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Hold private apper låst og skjult"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Trykk for å konfigurere eller åpne"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privat"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Innstillinger for Private Space"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lås / lås opp Private Space"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privat (ulåst)."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privat (låst)."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Lås"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Private Space-overgang"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Installer apper"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Installer"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Installer apper i privat område"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overflyt"</string>
</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 246e294..7880c36 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रिन"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s का हकमा एपसम्बन्धी जानकारी"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s को प्रयोगसम्बन्धी सेटिङ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"एपको पेयर सेभ गर्नुहोस्"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"यस डिभाइसमा यो एप पेयर प्रयोग गर्न मिल्दैन"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"होम स्क्रिनमा <xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट हालियो"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"सुझावहरू"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"तपाईंलाई दैनिक आवश्यक पर्ने एपहरू"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"तपाईंका निम्ति सिफारिस गरिएका समाचार"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"तपाईंको Chill Zone"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"आफूले तय गरेको तन्दुरुस्तीको लक्ष्यमा पुग्नुहोस्"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"मौसमको पूर्वानुमान प्राप्त गर्नुहोस्"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"तपाईंलाई निम्न कुराहरू पनि मन पर्न सक्छन्"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"अत्यावश्यक कुराहरू"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"समाचार तथा पत्रपत्रिकाहरू"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"मनोरञ्जन"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"सोसल मिडिया"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"तपाईंका लागि सिफारिस गरिएका"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"दायाँ भागमा <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> विजेटहरू, बायाँ भागमा खोज र विकल्पहरू"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# विजेट}other{# वटा विजेट}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# सर्टकट}other{# वटा सर्टकट}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"कामसम्बन्धी"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"वार्तालापहरू"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"नोट लेख्ने कार्य"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"उपयोगी जानकारी सजिलै प्राप्त गर्नुहोस्"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"एपहरू नखोलिकनै जानकारी प्राप्त गर्न तपाईं आफ्नो होम स्क्रिनमा विजेटहरू हाल्न सक्नुहुन्छ"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"हाल्नुहोस्"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट हाल्नुहोस्"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"विजेटका सेटिङ बदल्न ट्याप गर्नुहोस्"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"बुझेँ"</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>
@@ -84,19 +86,20 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"अनइन्स्टल गर्नुहोस्"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"एपसम्बन्धी जानकारी"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"निजी प्रोफाइलमा इन्स्टल गर्नुहोस्"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"एप अनइन्स्टल गर्नुहोस्"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"स्थापना गर्नुहोस्"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"एप सिफारिस नगरियोस्"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"एप सिफारिस नगर्नुहोस्"</string>
<string name="pin_prediction" msgid="4196423321649756498">"सिफारिस गरिएको एप पिन गर्नुहोस्"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"सर्टकट स्थापना गर्नेहोस्"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"प्रयोगकर्ताको हस्तक्षेप बिना एउटा एपलाई सर्टकटमा थप्नको लागि अनुमति दिनुहोस्।"</string>
- <string name="permlab_read_settings" msgid="5136500343007704955">"होम स्क्रिनका सेटिङ र सर्टकटहरू रिड गरियोस्"</string>
+ <string name="permlab_read_settings" msgid="5136500343007704955">"होम स्क्रिनका सेटिङ र सर्टकटहरू रिड गर्नुहोस्"</string>
<string name="permdesc_read_settings" msgid="4208061150510996676">"एपलाई होम स्क्रिनबाट सेटिङ र सर्टकटहरू रिड गर्ने अनुमति दिन्छ।"</string>
- <string name="permlab_write_settings" msgid="4820028712156303762">"होम स्क्रिनका सेटिङ र सर्टकटहरू राइट गरियोस्"</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_setup_text" msgid="8348374825537681407">"विजेटका सेटिङ"</string>
<string name="gadget_complete_setup_text" msgid="309040266978007925">"सेटअप पूरा गर्न ट्याप गर्नुहोस्"</string>
- <string name="uninstall_system_app_text" msgid="4172046090762920660">"यो प्रणाली एप हो र यसलाई स्थापना रद्द गर्न सकिँदैन।"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"यो सिस्टम एप हो र यसलाई स्थापना रद्द गर्न सकिँदैन।"</string>
<string name="folder_hint_text" msgid="5174843001373488816">"नाम सम्पादन गर्नुहोस्"</string>
<string name="disabled_app_label" msgid="6673129024321402780">"असक्षम पारिएको <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="dotted_app_label" msgid="1865617679843363410">"{count,plural, =1{{app_name} सँग सम्बन्धित # सूचना छ}other{{app_name} सँग सम्बन्धित # वटा सूचना छन्}}"</string>
@@ -125,7 +128,7 @@
<string name="title_change_settings" msgid="1376365968844349552">"सेटिङहरू बदल्नुहोस्"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"नोटिफिकेसन डट देखाउनुहोस्"</string>
<string name="developer_options_title" msgid="700788437593726194">"विकासकर्ताका लागि उपलब्ध विकल्पहरू"</string>
- <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"एपका आइकनहरू होम स्क्रिनमा राखियोस्"</string>
+ <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"एपका आइकनहरू होम स्क्रिनमा राख्नुहोस्"</string>
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नयाँ एपका लागि"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"हटाउनुहोस्"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> इन्स्टल गरिँदै छ, <xliff:g id="PROGRESS">%2$s</xliff:g> पूरा भयो"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड गर्दै, <xliff:g id="PROGRESS">%2$s</xliff:g> सम्पन्न"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> स्थापना गर्न प्रतीक्षा गर्दै"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> अभिलेखमा राखिएको छ। डाउनलोड गर्न ट्याप गर्नुहोस्।"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> अभिलेखमा राखिएको छ। डाउनलोड गरी रिस्टोर गर्न ट्याप गर्नुहोस्।"</string>
<string name="dialog_update_title" msgid="114234265740994042">"एप अपडेट गरिनु पर्छ"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"यो आइकनले जनाउने एप अपडेट गरिएको छैन। तपाईं यो सर्टकट फेरि अन गर्न म्यानुअल रूपमा अपडेट गर्न सक्नुहुन्छ वा आइकन नै हटाउनुहोस्।"</string>
<string name="dialog_update" msgid="2178028071796141234">"अपडेट गर्नुहोस्"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"फिल्टर"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"कार्य पूरा गर्न सकिएन: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"निजी स्पेस"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"निजी एपहरू लक गरिराख्नुहोस् र लुकाइराख्नुहोस्"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"सेटअप गर्न वा खोल्न ट्याप गर्नुहोस्"</string>
<string name="ps_container_title" msgid="4391796149519594205">"निजी"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"निजी स्पेससम्बन्धी सेटिङ"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"निजी स्पेस लक/अनलक गर्नुहोस्"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"निजी, अनलक गरिएको।"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"निजी, लक गरिएको।"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"लक गर्नुहोस्"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"निजी स्पेस ट्रान्जिसन गरिँदै छ"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"एपहरू इन्स्टल गर्नुहोस्"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"इन्स्टल गर्नुहोस्"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"निजी स्पेसमा एपहरू इन्स्टल गर्नुहोस्"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ओभरफ्लो"</string>
</resources>
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index e462ae0..d23f4d1 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -46,6 +46,10 @@
@android:color/system_neutral2_200</color>
<color name="widget_picker_collapse_handle_color_dark">
@android:color/system_neutral2_700</color>
+ <color name="widget_picker_add_button_background_color_dark">
+ @android:color/system_accent1_200</color>
+ <color name="widget_picker_add_button_text_color_dark">
+ @android:color/system_accent1_800</color>
<color name="work_fab_bg_color">
@android:color/system_accent1_200</color>
diff --git a/res/values-night-v34/colors.xml b/res/values-night-v34/colors.xml
new file mode 100644
index 0000000..af28119
--- /dev/null
+++ b/res/values-night-v34/colors.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <color name="widget_picker_secondary_surface_color_dark">
+ @android:color/system_surface_bright_dark</color>
+ <color name="widget_picker_header_app_title_color_dark">
+ @android:color/system_on_surface_dark</color>
+ <color name="widget_picker_header_app_subtitle_color_dark">
+ @android:color/system_on_surface_variant_dark</color>
+ <color name="widget_cell_title_color_dark">
+ @android:color/system_on_surface_dark</color>
+ <color name="widget_cell_subtitle_color_dark">
+ @android:color/system_on_surface_variant_dark</color>
+</resources>
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
index d41eb7e..c95722f 100644
--- a/res/values-night/styles.xml
+++ b/res/values-night/styles.xml
@@ -21,5 +21,14 @@
<style name="AddItemActivityTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
<item name="android:windowTranslucentStatus">true</item>
+ <!-- Add the dim background here, rather than in the activity layout as the window slides
+ in from the bottom, and we don't want the scrim to slide. -->
+ <item name="android:backgroundDimEnabled">true</item>
+ </style>
+
+ <style name="WidgetPickerActivityTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
+ <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_dark</item>
</style>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 19600a0..7b3f563 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets uitgezet in veilige modus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Snelkoppeling is niet beschikbaar"</string>
<string name="home_screen" msgid="5629429142036709174">"Startscherm"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Stel <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> in als de standaard startscherm-app in Instellingen"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gesplitst scherm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App-info voor %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Gebruiksinstellingen voor %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"App-paar opslaan"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Dit app-paar wordt niet ondersteund op dit apparaat"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Vouw het apparaat open om dit app-paar te gebruiken"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"App-paar is niet beschikbaar"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Tik en houd vast om een widget te verplaatsen."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dubbeltik en houd vast om een widget te verplaatsen of aangepaste acties te gebruiken."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breed en %2$d hoog"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d breed bij %3$d hoog"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Houd je vinger op de widget om deze te verplaatsen op het startscherm"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Toevoegen aan startscherm"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> toegevoegd aan startscherm"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggesties"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Je dagelijkse essentials"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Nieuws voor jou"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Je chillzone"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Behaal je fitnessdoelen"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Blijf het weer een stap voor"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Misschien ook interessant"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essentials"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Nieuws en tijdschriften"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entertainment"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sociaal"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Voorgesteld voor jou"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-widgets aan de rechterkant, zoeken en opties aan de linkerkant"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# snelkoppeling}other{# snelkoppelingen}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Werk"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Gesprekken"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Aantekeningen maken"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Nuttige informatie binnen handbereik"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Als je informatie wilt krijgen zonder apps te openen, kun je widgets toevoegen aan je startscherm"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Toevoegen"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> toevoegen"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tik om de widgetinstellingen te wijzigen"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Widgetinstellingen wijzigen"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Apps zoeken"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Apps laden…"</string>
@@ -84,9 +86,10 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deïnstalleren"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"App-info"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Privé installeren"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"App verwijderen"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Installeren"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Geen app voorstellen"</string>
- <string name="pin_prediction" msgid="4196423321649756498">"Vastzetvoorspelling"</string>
+ <string name="pin_prediction" msgid="4196423321649756498">"Voorspelling vastzetten"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"Snelle links instellen"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Een app toestaan snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker."</string>
<string name="permlab_read_settings" msgid="5136500343007704955">"instellingen en snelkoppelingen op startscherm lezen"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installeren, <xliff:g id="PROGRESS">%2$s</xliff:g> voltooid"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> wordt gedownload, <xliff:g id="PROGRESS">%2$s</xliff:g> voltooid"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> wacht op installatie"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> is gearchiveerd Tik om te downloaden."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> is gearchiveerd. Tik om te downloaden en te herstellen."</string>
<string name="dialog_update_title" msgid="114234265740994042">"App-update vereist"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"De app voor dit icoon is niet geüpdatet. Je kunt handmatig updaten om deze snelkoppeling weer aan te zetten of het icoon verwijderen."</string>
<string name="dialog_update" msgid="2178028071796141234">"Updaten"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filteren"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Mislukt: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privéruimte"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Houd privé-apps vergrendeld en verborgen"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Tik om in te stellen of te openen"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privé"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Instellingen voor privéruimte"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Privéruimte vergrendelen/ontgrendelen"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privé, niet vergrendeld."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privé, vergrendeld."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Vergrendelen"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Overschakelen naar privéruimte"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Apps installeren"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Installeren"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Apps installeren in privégedeelte"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overloop"</string>
</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 44a85ca..33b7664 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -27,27 +27,30 @@
<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>କୁ ଡିଫଲ୍ଟ Home ଆପ ଭାବରେ ସେଟ କରନ୍ତୁ"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ସ୍କ୍ରିନକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ପାଇଁ ଆପ ସୂଚନା"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ପାଇଁ ବ୍ୟବହାର ସେଟିଂସ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ଆପ ପେୟାର ସେଭ କରନ୍ତୁ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ଏହି ଆପ ପେୟାର ଏ ଡିଭାଇସରେ ସମର୍ଥିତ ନୁହେଁ"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>ର ୱିଜେଟ ହୋମ ସ୍କ୍ରିନରେ ଯୋଡ଼ାଗଲା"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"ପରାମର୍ଶଗୁଡ଼ିକ"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"ଆପଣଙ୍କ ଦୈନନ୍ଦିନ ଅତ୍ୟାବଶ୍ୟକୀୟ"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"ଆପଣଙ୍କ ପାଇଁ ନ୍ୟୁଜ"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"ଆପଣଙ୍କ ଚିଲ ଜୋନ"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"ଆପଣଙ୍କ ଫିଟନେସ ଲକ୍ଷ୍ୟରେ ପହଞ୍ଚନ୍ତୁ"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"ପାଣିପାଗ ବିଷୟରେ ଆଗୁଆ ସୂଚନା ପାଆନ୍ତୁ"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"ଆପଣ ମଧ୍ୟ ପସନ୍ଦ କରିପାରନ୍ତି"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ଅତ୍ୟାବଶ୍ୟକୀୟ"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ନ୍ୟୁଜ ଓ ମାଗାଜିନ"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"ମନୋରଞ୍ଜନ"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"ସୋସିଆଲ"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"ଆପଣଙ୍କ ପାଇଁ ପ୍ରସ୍ତାବିତ"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"ଡାହାଣରେ <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ୱିଜେଟଗୁଡ଼ିକ ଅଛି, ବାମରେ ସର୍ଚ୍ଚ ଓ ବିକଳ୍ପଗୁଡ଼ିକ ଅଛି"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# ୱିଜେଟ}other{# ୱିଜେଟ}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{#ଟି ସର୍ଟକଟ୍}other{#ଟି ସର୍ଟକଟ୍}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ୱାର୍କ"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"ନୋଟ-ଟେକିଂ"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"ଉପଯୋଗୀ ସୂଚନା ଆପଣଙ୍କ ପାଖରେ ସହଜରେ ଉପଲବ୍ଧ"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ଆପ୍ସକୁ ନଖୋଲି ସୂଚନା ପାଇବା ପାଇଁ, ଆପଣ ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନରେ ୱିଜେଟଗୁଡ଼ିକୁ ଯୋଗ କରିପାରିବେ"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"ଯୋଗ କରନ୍ତୁ"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ୱିଜେଟ ସେଟିଂସ ପରିବର୍ତ୍ତନ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"ବୁଝିଗଲି"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"ଅନଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ଆପ ସୂଚନା"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ପ୍ରାଇଭେଟରେ ଇନଷ୍ଟଲ କର"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ଆପ ଅନଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ଇନଷ୍ଟଲ୍ କରନ୍ତୁ"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ଆପ ପରାମର୍ଶ ଦିଅନ୍ତୁ ନାହିଁ"</string>
<string name="pin_prediction" msgid="4196423321649756498">"ପୂର୍ବାନୁମାନକୁ ପିନ୍ କରନ୍ତୁ"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ଇନଷ୍ଟଲ୍ କରାଯାଉଛି, <xliff:g id="PROGRESS">%2$s</xliff:g> ସମ୍ପୂର୍ଣ୍ଣ ହୋଇଛି"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ଡାଉନଲୋଡ୍ ହେଉଛି, <xliff:g id="PROGRESS">%2$s</xliff:g> ସମ୍ପୂର୍ଣ୍ଣ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ଇନଷ୍ଟଲ୍ ହେବାକୁ ଅପେକ୍ଷା କରିଛି"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g>କୁ ଆର୍କାଇଭ କରାଯାଇଛି। ଡାଉନଲୋଡ୍ କରିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ।"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g>କୁ ଆର୍କାଇଭ କରାଯାଇଛି। ଡାଉନଲୋଡ ଏବଂ ରିଷ୍ଟୋର କରିବା ପାଇଁ ଟାପ କରନ୍ତୁ।"</string>
<string name="dialog_update_title" msgid="114234265740994042">"ଆପକୁ ଅପଡେଟ କରିବା ଆବଶ୍ୟକ"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"ଏହି ଆଇକନ ପାଇଁ ଆପକୁ ଅପଡେଟ କରାଯାଇନାହିଁ। ଏହି ସର୍ଟକଟକୁ ପୁଣି-ସକ୍ଷମ କରିବା ପାଇଁ ଆପଣ ମାନୁଆଲୀ ଅପଡେଟ କରିପାରିବେ କିମ୍ବା ଆଇକନଟିକୁ କାଢ଼ି ଦେଇପାରିବେ।"</string>
<string name="dialog_update" msgid="2178028071796141234">"ଅପଡେଟ କରନ୍ତୁ"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ଫିଲ୍ଟର୍"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"ବିଫଳ ହୋଇଛି: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"ପ୍ରାଇଭେଟ ସ୍ପେସ"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"ପ୍ରାଇଭେଟ ଆପ୍ସକୁ ଲକ ଏବଂ ଲୁକ୍କାୟିତ ରଖନ୍ତୁ"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"ସେଟ ଅପ କରିବା କିମ୍ବା ଖୋଲିବାକୁ ଟାପ କରନ୍ତୁ"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ପ୍ରାଇଭେଟ"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"ପ୍ରାଇଭେଟ ସ୍ପେସ ସେଟିଂସ"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"ପ୍ରାଇଭେଟ ସ୍ପେସକୁ ଲକ/ଅନଲକ କରନ୍ତୁ"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ପ୍ରାଇଭେଟ, ଅନଲକ କରାଯାଇଛି।"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ପ୍ରାଇଭେଟ, ଲକ କରାଯାଇଛି।"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ଲକ କରନ୍ତୁ"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"ପ୍ରାଇଭେଟ ସ୍ପେସ ଟ୍ରାଞ୍ଜିସନିଂ"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ଆପ୍ ଇନଷ୍ଟଲ୍ କରନ୍ତୁ"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"ଆପ୍ସକୁ ପ୍ରାଇଭେଟ ସ୍ପେସରେ ଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ଓଭରଫ୍ଲୋ"</string>
</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 7ed539f..71c25eb 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ਲਈ ਐਪ ਜਾਣਕਾਰੀ"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ਲਈ ਵਰਤੋਂ ਸੈਟਿੰਗਾਂ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ਐਪ ਜੋੜਾਬੱਧ ਰੱਖਿਅਤ ਕਰੋ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ਇਸ ਐਪ ਜੋੜਾਬੱਧ ਦਾ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਮਰਥਨ ਨਹੀਂ ਕੀਤਾ ਜਾਂਦਾ"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ਵਿਜੇਟ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"ਸੁਝਾਅ"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"ਤੁਹਾਡੇ ਰੋਜ਼ਾਨਾ ਦੀਆਂ ਲੋੜੀਂਦੀਆਂ ਚੀਜ਼ਾਂ"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"ਤੁਹਾਡੇ ਲਈ ਖਬਰਾਂ"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"ਤੁਹਾਡੇ ਲਈ ਸਕੂਨਮਈ ਖੇਤਰ"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"ਆਪਣੇ ਫਿੱਟਨੈੱਸ ਸੰਬੰਧੀ ਟੀਚੇ ਹਾਸਲ ਕਰੋ"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"ਮੌਸਮ ਬਾਰੇ ਅੱਪ-ਟੂ-ਡੇਟ ਰਹੋ"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"ਸ਼ਾਇਦ ਤੁਸੀਂ ਇਹ ਵੀ ਪਸੰਦ ਕਰੋ"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ਲੋੜੀਂਦੀਆਂ"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ਖਬਰਾਂ ਅਤੇ ਰਸਾਲੇ"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"ਮਨੋਰੰਜਨ"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"ਸੋਸ਼ਲ"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"ਤੁਹਾਡੇ ਲਈ ਸੁਝਾਅ"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ਵਿਜੇਟ ਸੱਜੇ ਪਾਸੇ ਹਨ, ਖੋਜ ਵਿਜੇਟ ਅਤੇ ਹੋਰ ਵਿਕਲਪ ਖੱਬੇ ਪਾਸੇ ਹਨ"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# ਵਿਜੇਟ}one{# ਵਿਜੇਟ}other{# ਵਿਜੇਟ}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# ਸ਼ਾਰਟਕੱਟ}one{# ਸ਼ਾਰਟਕੱਟ}other{# ਸ਼ਾਰਟਕੱਟ}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ਕਾਰਜ-ਸਥਾਨ"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"ਗੱਲਾਂਬਾਤਾਂ"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"ਨੋਟ ਬਣਾਉਣਾ"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"ਮਹੱਤਵਪੂਰਨ ਜਾਣਕਾਰੀ ਤੁਰੰਤ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ਐਪਾਂ ਨੂੰ ਖੋਲ੍ਹੇ ਬਿਨਾਂ ਜਾਣਕਾਰੀ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ, ਤੁਸੀਂ ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰ ਸਕਦੇ ਹੋ"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"ਸ਼ਾਮਲ ਕਰੋ"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ਵਿਜੇਟ ਸੈਟਿੰਗਾਂ ਨੂੰ ਬਦਲਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"ਸਮਝ ਲਿਆ"</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>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"ਹਟਾਓ"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"ਅਣਸਥਾਪਤ ਕਰੋ"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ਐਪ ਜਾਣਕਾਰੀ"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ਨਿੱਜੀ ਵਜੋਂ ਸਥਾਪਤ ਕਰੋ"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ਪ੍ਰਾਈਵੇਟ ਵਜੋਂ ਸਥਾਪਤ ਕਰੋ"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ਐਪ ਅਣਸਥਾਪਤ ਕਰੋ"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ਸਥਾਪਤ ਕਰੋ"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ਐਪ ਦਾ ਸੁਝਾਅ ਨਾ ਦਿਓ"</string>
<string name="pin_prediction" msgid="4196423321649756498">"ਪੂਰਵ-ਅਨੁਮਾਨ ਪਿੰਨ ਕਰੋ"</string>
@@ -124,7 +127,7 @@
<string name="msg_missing_notification_access" msgid="281113995110910548">"ਸੂਚਨਾ ਬਿੰਦੂਆਂ ਦਿਖਾਉਣ ਲਈ, <xliff:g id="NAME">%1$s</xliff:g> ਲਈ ਐਪ ਸੂਚਨਾਵਾਂ ਚਾਲੂ ਕਰੋ"</string>
<string name="title_change_settings" msgid="1376365968844349552">"ਸੈਟਿੰਗਾਂ ਬਦਲੋ"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"ਸੂਚਨਾ ਬਿੰਦੂ ਦਿਖਾਓ"</string>
- <string name="developer_options_title" msgid="700788437593726194">"ਵਿਕਾਸਕਾਰ ਚੋਣਾਂ"</string>
+ <string name="developer_options_title" msgid="700788437593726194">"ਵਿਕਾਸਕਾਰ ਵਿਕਲਪ"</string>
<string name="auto_add_shortcuts_label" msgid="4926805029653694105">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਐਪ ਪ੍ਰਤੀਕਾਂ ਨੂੰ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ਨਵੀਆਂ ਐਪਾਂ ਲਈ"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"ਅਗਿਆਤ"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ਨੂੰ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ, <xliff:g id="PROGRESS">%2$s</xliff:g> ਪੂਰਾ ਹੋਇਆ"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ਡਾਉਨਲੋਡ ਹੋਰ ਰਿਹਾ ਹੈ, <xliff:g id="PROGRESS">%2$s</xliff:g> ਸੰਪੂਰਣ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ਸਥਾਪਤ ਕਰਨ ਦੀ ਉਡੀਕ ਕਰ ਰਿਹਾ ਹੈ"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> ਪੁਰਾਲੇਖਬੱਧ ਹੈ। ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> ਪੁਰਾਲੇਖਬੱਧ ਹੈ। ਡਾਊਨਲੋਡ ਅਤੇ ਮੁੜ-ਬਹਾਲ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="dialog_update_title" msgid="114234265740994042">"ਐਪ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"ਇਸ ਪ੍ਰਤੀਕ ਲਈ ਐਪ ਨੂੰ ਅੱਪਡੇਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ। ਇਸ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਜਾਂ ਪ੍ਰਤੀਕ ਨੂੰ ਹਟਾਉਣ ਲਈ ਤੁਸੀਂ ਹੱਥੀਂ ਅੱਪਡੇਟ ਕਰ ਸਕਦੇ ਹੋ।"</string>
<string name="dialog_update" msgid="2178028071796141234">"ਅੱਪਡੇਟ ਕਰੋ"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"ਰੋਕ ਹਟਾਓ"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ਫਿਲਟਰ"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"ਇਹ ਕਾਰਵਾਈ ਅਸਫਲ ਹੋਈ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"ਨਿੱਜੀ ਸਪੇਸ"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"ਨਿੱਜੀ ਐਪਾਂ ਨੂੰ ਲਾਕ ਕਰ ਕੇ ਅਦਿੱਖ ਰੱਖੋ"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"ਸੈੱਟਅੱਪ ਕਰਨ ਜਾਂ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ਨਿੱਜੀ"</string>
- <string name="ps_container_settings" msgid="6059734123353320479">"ਨਿੱਜੀ ਸਪੇਸ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"ਨਿੱਜੀ ਸਪੇਸ ਨੂੰ ਲਾਕ/ਅਣਲਾਕ ਕਰੋ"</string>
+ <string name="ps_container_settings" msgid="6059734123353320479">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ਨਿੱਜੀ, ਅਣਲਾਕ ਕੀਤਾ ਗਿਆ।"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ਨਿੱਜੀ, ਲਾਕ ਕੀਤਾ ਗਿਆ।"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ਲਾਕ ਕਰੋ"</string>
- <string name="ps_container_transition" msgid="8667331812048014412">"ਨਿੱਜੀ ਸਪੇਸ ਨੂੰ ਤਬਦੀਲ ਕਰਨਾ"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ਐਪਾਂ ਸਥਾਪਤ ਕਰੋ"</string>
+ <string name="ps_container_transition" msgid="8667331812048014412">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਨੂੰ ਤਬਦੀਲ ਕਰਨਾ"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ਸਥਾਪਤ ਕਰੋ"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਵਿੱਚ ਐਪਾਂ ਸਥਾਪਤ ਕਰੋ"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ਓਵਰਫ਼ਲੋ"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b943602..587c40f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widżety są wyłączone w trybie bezpiecznym"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Skrót nie jest dostępny"</string>
<string name="home_screen" msgid="5629429142036709174">"Ekran główny"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Otwórz Ustawienia i ustaw aplikację <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> jako domyślną aplikację ekranu głównego"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podziel ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacje o aplikacji: %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s – ustawienia użycia"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Zapisz parę aplikacji"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ta para aplikacji nie jest obsługiwana na tym urządzeniu"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Otwórz urządzenie, aby użyć tej pary aplikacji"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Para aplikacji jest niedostępna"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Naciśnij i przytrzymaj, aby przenieść widżet."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Naciśnij dwukrotnie i przytrzymaj, aby przenieść widżet lub użyć działań niestandardowych."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<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_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>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Niezbędne na co dzień"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Wiadomości dla Ciebie"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Strefa relaksu"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Zadbaj o swoją formę"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Nie daj się zaskoczyć pogodzie"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"To też może Cię zainteresować"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Najbardziej przydatne"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Wiadomości i czasopisma"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Rozrywka"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Społecznościowe"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Proponowane dla Ciebie"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widżety (<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>) po prawej, wyszukiwanie i opcje po lewej"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widżet}few{# widżety}many{# widżetów}other{# widżetu}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# skrót}few{# skróty}many{# skrótów}other{# skrótu}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Służbowe"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Rozmowy"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Notatki"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Użyteczne informacje w zasięgu ręki"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Możesz dodać widżety do ekranu głównego, aby uzyskiwać informacje bez otwierania aplikacji"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodaj widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Kliknij, aby zmienić ustawienia widżetu"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Zmień ustawienia widżetu"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Wyszukaj aplikacje"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Ładuję aplikacje…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Odinstaluj"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"O aplikacji"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Zainstaluj prywatnie"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Odinstaluj aplikację"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Zainstaluj"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Nie proponuj aplikacji"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Przypnij podpowiedź"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instaluję aplikację <xliff:g id="NAME">%1$s</xliff:g>, postęp: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Pobieranie elementu <xliff:g id="NAME">%1$s</xliff:g>, ukończono: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> oczekuje na instalację"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Aplikacja <xliff:g id="NAME">%1$s</xliff:g> jest zarchiwizowana. Kliknij, aby ją pobrać."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Aplikacja <xliff:g id="NAME">%1$s</xliff:g> jest zarchiwizowana. Kliknij, aby ją pobrać i przywrócić."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Wymagana aktualizacja aplikacji"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Aplikacja z tą ikoną nie jest aktualizowana. Możesz zaktualizować ją ręcznie, aby ponownie uruchomić ten skrót, lub usunąć ikonę."</string>
<string name="dialog_update" msgid="2178028071796141234">"Aktualizuj"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Cofnij wstrzymywanie"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtruj"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Niepowodzenie: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Obszar prywatny"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Pozostaw aplikacje prywatne zablokowane i ukryte"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Przestrzeń prywatna"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Kliknij, aby skonfigurować lub otworzyć"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Prywatne"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Ustawienia obszaru prywatnego"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Zablokuj/odblokuj obszar prywatny"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Prywatna, bez blokady."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Prywatna, zablokowana."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Zablokuj"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Przenoszenie obszaru prywatnego"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instalowanie aplikacji"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Zainstaluj"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Zainstaluj aplikacje w przestrzeni prywatnej"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Rozwiń menu"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index da08937..67d93de 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no Modo de segurança"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
<string name="home_screen" msgid="5629429142036709174">"Página inicial"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Defina a app <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como a app inicial predefinida nas Definições"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecrã dividido"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações da app para %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Definições de utilização para %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Guardar par de apps"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Este par de apps não é suportado neste dispositivo"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Abra o dispositivo para usar este par de apps"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"O par de apps não está disponível"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Toque sem soltar para mover um widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toque duas vezes sem soltar para mover um widget ou utilizar ações personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largura por %2$d de altura"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d de largura por %3$d de altura"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Toque sem soltar no widget para o mover no ecrã principal"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Adicionar ao ecrã principal"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> adicionado ao ecrã principal"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Sugestões"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Os seus essenciais do dia a dia"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Notícias para si"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"A sua zona de relaxamento"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Atingir os seus objetivos de fitness"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Ficar a par da meteorologia"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Também poderá gostar de"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essenciais"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Notícias e revistas"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entretenimento"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Redes sociais"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sugestões para si"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgets de <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> à direita, pesquisa e opções à esquerda"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# atalho}other{# atalhos}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabalho"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Tomar notas"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Informações úteis à sua disposição"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Para obter informações sem abrir apps, pode adicionar widgets ao seu ecrã principal"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Adicionar"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Adicione o widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Toque para alterar as definições do widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Alterar definições do widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Pesquisar apps"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"A carregar aplicações…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Info. da app"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instalar em privado"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Desinstalar app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instalar"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Não sugerir app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Fixar previsão"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"A instalar <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"A transferir o <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"A aguardar a instalação do <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"A app <xliff:g id="NAME">%1$s</xliff:g> está arquivada. Toque para transferir."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"A app <xliff:g id="NAME">%1$s</xliff:g> está arquivada. Toque para transferir e restaurar."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Atualização da app necessária"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"A app deste ícone não está atualizada. Pode atualizar manualmente para reativar este atalho ou remover o ícone."</string>
<string name="dialog_update" msgid="2178028071796141234">"Atualizar"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrar"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Falhou: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Espaço privado"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Mantém as apps privadas bloqueadas e ocultas"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Toque para configurar ou abrir"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privado"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Definições do espaço privado"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloquear/desbloquear espaço privado"</string>
- <string name="ps_container_lock_title" msgid="2640257399982364682">"Fechadura"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privado, desbloqueado."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privado, bloqueado."</string>
+ <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloqueio"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Transição do espaço privado"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instalar apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalar"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instale apps no espaço privado"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Menu adicional"</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 10111aa..4302588 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no modo de segurança"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
<string name="home_screen" msgid="5629429142036709174">"Início"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Definir <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como app de início padrão nas Configurações"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Tela dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações do app %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Configurações de uso de %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Salvar par de apps"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Este Par de apps não está disponível no dispositivo"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Abra o dispositivo para usar este Par de apps"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"O Par de apps não está disponível"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Toque e pressione para mover um widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toque duas vezes e mantenha a tela pressionada para mover um widget ou usar ações personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largura por %2$d de altura"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>: %2$d de largura por %3$d de altura"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Toque no widget e o pressione para definir a posição dele na tela inicial"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Adicionar à tela inicial"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> adicionado à tela inicial"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Sugestões"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Seus itens diários essenciais"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Notícias para você"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Sua zona de relaxamento"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Alcance seus objetivos fitness"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Fique por dentro da previsão do tempo"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Você também pode gostar de"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Essenciais"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Notícias e revistas"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entretenimento"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Social"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sugestões para você"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgets da <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> à direita, pesquisa e opções à esquerda"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}one{# widget}other{# widgets}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# atalho}one{# atalho}other{# atalhos}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabalho"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Anotações"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Informações úteis ao seu alcance"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Para acessar informações sem precisar abrir os apps, adicione widgets à sua tela inicial"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Adicionar"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Adicionar o widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Toque para mudar as configurações do widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Ok"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Mudar as configurações do widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Pesquisar apps"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Carregando apps…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Informações do app"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instalar em particular"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Desinstalar app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instalar"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Não sugerir esse app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Fixar previsão"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instalando <xliff:g id="NAME">%1$s</xliff:g>. <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Fazendo download de <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Aguardando instalação de <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"O app <xliff:g id="NAME">%1$s</xliff:g> está arquivado. Toque para baixar."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"O app <xliff:g id="NAME">%1$s</xliff:g> está arquivado. Toque para baixar e restaurar."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Atualização obrigatória do app"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"O app desse ícone não está atualizado. Você pode remover o ícone ou atualizar o app manualmente para reativar esse atalho."</string>
<string name="dialog_update" msgid="2178028071796141234">"Atualizar"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Ativar"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrar"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Falha: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Espaço particular"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Mantenha apps particulares bloqueados e ocultos"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Espaço privado"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Toque para configurar ou abrir"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Particular"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Configurações do Espaço particular"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloquear/desbloquear o Espaço particular"</string>
- <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloquear"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privada, desbloqueado."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privada, bloqueado."</string>
+ <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloqueio"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Espaço particular em transição"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instalar apps"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalar"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalar apps no espaço privado"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Balão flutuante"</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 9eab793..d9e0413 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgeturile sunt dezactivate în modul de siguranță"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Comanda rapidă nu este disponibilă"</string>
<string name="home_screen" msgid="5629429142036709174">"Pagina de pornire"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Setează <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> drept aplicație ecran de pornire prestabilită, în Setări"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecran împărțit"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informații despre aplicație pentru %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Setări de utilizare pentru %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Salvează perechea de aplicații"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Perechea de aplicații nu este acceptată pe acest dispozitiv"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Deschide dispozitivul ca să folosești această pereche de aplicații"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Perechea de aplicații nu este disponibilă"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Atinge și ține apăsat pentru a muta un widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Atinge de două ori și ține apăsat pentru a muta un widget sau folosește acțiuni personalizate."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d lățime și %2$d înălțime"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widgetul <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widgetul <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d lățime x %3$d înălțime"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Atinge lung widgetul pentru a-l muta pe ecranul de pornire"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Adaugă pe ecranul de pornire"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widgetul <xliff:g id="WIDGET_NAME">%1$s</xliff:g> a fost adăugat pe ecranul de pornire"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Sugestii"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Instrumente esențiale de zi cu zi"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Știri pentru tine"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Zona de relaxare"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Atinge-ți obiectivele de fitness"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Fii la curent cu prognoza meteo"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"S-ar putea să îți placă și"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Esențiale"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Știri și reviste"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Divertisment"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Rețele sociale"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sugerate pentru tine"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgeturi pentru <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> în dreapta, căutare și opțiuni în stânga"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}few{# widgeturi}other{# de widgeturi}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# comandă rapidă}few{# comenzi rapide}other{# de comenzi rapide}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Serviciu"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversații"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Luare de notițe"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Informații utile la îndemâna ta"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Pentru a primi informații fără să deschizi aplicațiile, poți adăuga widgeturi pe ecranul de pornire"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Adaugă"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Adaugă widgetul <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Atinge ca să schimbi setările pentru widgeturi"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Modifică setările pentru widgeturi"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Caută aplicații"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Se încarcă aplicații…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Dezinstalează"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Informații despre aplicații"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instalează în privat"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Dezinstalează aplicația"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instalează"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Nu sugera aplicația"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Fixează predicția"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> se instalează, <xliff:g id="PROGRESS">%2$s</xliff:g> finalizat"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se descarcă (finalizat <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> așteaptă instalarea"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> s-a arhivat. Atinge pentru a descărca."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> s-a arhivat. Atinge pentru a descărca și restabili."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Este necesară actualizarea aplicației"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Aplicația pentru această pictogramă nu este actualizată. Poți să actualizezi manual ca să reactivezi comanda rapidă sau să elimini pictograma."</string>
<string name="dialog_update" msgid="2178028071796141234">"Actualizează"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtru"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Eșuare: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Spațiu privat"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Păstrează aplicațiile private blocate și ascunse"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Atinge pentru a configura sau a deschide"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privat"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Setări spațiu privat"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Blochează / deblochează spațiul privat"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privat, deblocat."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privat, blocat."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Blochează"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Tranziție pentru spațiul privat"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instalează aplicații"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalează"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalează aplicații în Spațiul privat"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Suplimentar"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 0b42966..64a21f7 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделить экран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Сведения о приложении \"%1$s\""</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки использования приложения \"%1$s\""</string>
<string name="save_app_pair" msgid="5647523853662686243">"Сохранить приложения"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Одновременно использовать эти два приложения на устройстве нельзя."</string>
<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="widget_dims_format" msgid="2370757736025621599">"%1$d x %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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Виджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\" добавлен на главный экран"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Подсказки"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Самое важное"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Новости для вас"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Развлечение и общение"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Ваши фитнес-цели"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Прогноз погоды"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Другие рекомендации"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Основное"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Новости и журналы"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Развлечения"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Общение"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Рекомендации"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Виджеты приложения \"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>\" находятся справа, а панель поиска и настройки – слева"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# виджет}one{# виджет}few{# виджета}many{# виджетов}other{# виджета}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# ярлык}one{# ярлык}few{# ярлыка}many{# ярлыков}other{# ярлыка}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Рабочие виджеты"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Разговоры"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Создание заметок"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Вся нужная информация перед глазами"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Чтобы не открывать приложения каждый раз, когда нужна информация, добавьте виджеты на главный экран."</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Добавить"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Добавить виджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Нажмите, чтобы изменить настройки виджета"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"ОК"</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>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Убрать"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Удалить"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"О приложении"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Приватная установка"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Частная установка"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Удалить приложение"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Установить"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Не рекомендовать"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Закрепить рекомендацию"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Установка приложения \"<xliff:g id="NAME">%1$s</xliff:g>\" (выполнено <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Скачивается \"<xliff:g id="NAME">%1$s</xliff:g>\" (<xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Ожидание установки \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Приложение \"<xliff:g id="NAME">%1$s</xliff:g>\" находится в архиве. Нажмите, чтобы скачать"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Приложение \"<xliff:g id="NAME">%1$s</xliff:g>\" находится в архиве. Нажмите, чтобы скачать его и восстановить"</string>
<string name="dialog_update_title" msgid="114234265740994042">"Обновите приложение"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Эта версия приложения устарела. Обновите его вручную, чтобы снова пользоваться ярлыком, или удалите значок."</string>
<string name="dialog_update" msgid="2178028071796141234">"Обновить"</string>
@@ -182,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Возобновить"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Фильтр"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Не удалось выполнить действие (<xliff:g id="WHAT">%1$s</xliff:g>)."</string>
- <string name="private_space_label" msgid="2359721649407947001">"Личное пространство"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Приложения в личном пространстве скрыты и доступны только вам"</string>
- <string name="ps_container_title" msgid="4391796149519594205">"Доступно только вам"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Частное пространство"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Нажмите, чтобы настроить или открыть"</string>
+ <string name="ps_container_title" msgid="4391796149519594205">"Частный профиль"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Настройки личного пространства"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Блокировка и разблокировка личного пространства"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Личное, разблокировано."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Личное, заблокировано."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Блокировка"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Переход к личному пространству"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Установить приложения"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Установить"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Установить приложения в личном пространстве"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Дополнительное меню"</string>
</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index ad5d158..71efc03 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"බෙදුම් තිරය"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s සඳහා යෙදුම් තතු"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s සඳහා භාවිත සැකසීම්"</string>
<string name="save_app_pair" msgid="5647523853662686243">"යෙදුම් යුගල සුරකින්න"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"මෙම යෙදුම් යුගලය මෙම උපාංගයෙහි සහාය නොදක්වයි"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> විජට්ටුව මුල් පිටු තිරය වෙත එක් කරන ලදි"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"යෝජනා"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"ඔබේ දෛනික අත්යවශ්යාංග"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"ඔබ වෙනුවෙන් පුවත්"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"ඔබේ නිවුණු කලාපය"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"ඔබේ යෝග්යතා ඉලක්ක ළඟා කර ගන්න"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"කාලගුණයට ඉදිරියෙන් සිටින්න"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"ඔබ මේවාට ද කැමති විය හැක"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"අත්යවශ්යාංග"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"පුවත් සහ සඟරා"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"විනෝදාස්වාදය"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"සමාජයීය"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"ඔබ සඳහා යෝජිත"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"දකුණේ <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> විජට්, වමේ සෙවීම සහ විකල්ප"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{විජට් #}one{විජට් #}other{විජට් #}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{කෙටි මං #}one{කෙටි මං #}other{කෙටි මං #}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"කාර්යාලය"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"සංවාද"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"සටහන් කර ගැනීම"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"ප්රයෝජනවත් තොරතුරු ඔබගේ ඇඟිලි තුඩු අග"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"යෙදුම් විවෘත නොකර තොරතුරු ලබා ගැනීම සඳහා, ඔබට ඔබගේ මුල් තිරයට විජට් එක් කළ හැකිය"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"එක් කරන්න"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> විජට්ටුව එක් කරන්න"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"විජට් සැකසීම් වෙනස් කිරීමට තට්ටු කරන්න"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"තේරුණා"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"අස්ථාපනය කරන්න"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"යෙදුම් තොරතුරු"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"පෞද්ගලිකව ස්ථාපනය කරන්න"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"යෙදුම අස්ථාපනය කරන්න"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ස්ථාපනය කරන්න"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"යෙදුම යෝජනා නොකරන්න"</string>
<string name="pin_prediction" msgid="4196423321649756498">"පුරෝකථනය අමුණන්න"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ස්ථාපනය කරමින්, <xliff:g id="PROGRESS">%2$s</xliff:g> සම්පූර්ණයි"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> බාගත කරමින්, <xliff:g id="PROGRESS">%2$s</xliff:g> සම්පූර්ණයි"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ස්ථාපනය කිරීමට බලා සිටිමින්"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> ලේඛනාරක්ෂණය කර ඇත. බාගැනීමට තට්ටු කරන්න"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> සංරක්ෂිතයි. බා ගෙන ප්රතිසාධන කිරීමට තට්ටු කරන්න."</string>
<string name="dialog_update_title" msgid="114234265740994042">"යෙදුම් යාවත්කාලීනයක් අවශ්යයි"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"මෙම නිරූපකය සඳහා යෙදුම යාවත්කාලීන කර නැත. ඔබට මෙම කෙටි මඟ යළි සබල කිරීමට හෝ නිරූපකය ඉවත් කිරීමට හස්තීයව යාවත්කාලීන කළ හැකිය."</string>
<string name="dialog_update" msgid="2178028071796141234">"යාවත්කාලීන කරන්න"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"පෙරහන"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"අසාර්ථකයි: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"පෞද්ගලික ඉඩ"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"පෞද්ගලික යෙදුම් අගුලු දමා සඟවා තබා ගන්න"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"පිහිටුවීමට හෝ විවෘත කිරීමට තට්ටු කරන්න"</string>
<string name="ps_container_title" msgid="4391796149519594205">"පෞද්ගලික"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"පෞද්ගලික අවකාශ සැකසීම්"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"පෞද්ගලික අවකාශය අගුළු දමන්න/අගුළු හරින්න"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"පුද්ගලි, අගුලු හරින ලදි."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"පුද්ගලික, අගුලු දමන ලදි."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"අගුළු දමන්න"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"පෞද්ගලික අවකාශ සංක්රමණය"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"යෙදුම් ස්ථාපනය කරන්න"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ස්ථාපන කරන්න"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"පෞද්ගලික අවකාශයට යෙදුම් ස්ථාපනය කරන්න"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"පිටාර යාම"</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index b4b67a1..43bc29c 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikácie sú v núdzovom režime zakázané"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Skratky nie sú k dispozícii"</string>
<string name="home_screen" msgid="5629429142036709174">"Domov"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nastaviť aplikáciu <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ako predvolenú vstupnú aplikáciu v Nastaveniach"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdeliť obrazovku"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informácie o aplikácii pre %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavenia používania pre %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Uložiť pár aplikácií"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Tento pár aplikácií nie je v tomto zariadení podporovaný"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Ak chcete používať tento pár aplikácií, rozložte zariadenie"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Pár aplikácií nie je k dispozícii"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Pridržaním presuňte miniaplikáciu."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvojitým klepnutím a pridržaním presuňte miniaplikáciu alebo použite vlastné akcie."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"šírka %1$d, výška %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Miniaplikácia <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Miniaplikácia <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d na šírku, %3$d na výšku"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Pridržaním môžete miniaplikáciu posúvať po ploche"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Pridať na plochu"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Na plochu bola pridaná miniaplikácia <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Návrhy"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Vaše každodenné základné položky"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Vaše správy"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Vaša komfortná zóna"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Dosiahnite svoje kondičné ciele"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Získavajte informácie o počasí v predstihu"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Mohlo by sa vám páčiť"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Všetko dôležité"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Noviny a časopisy"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Zábava"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sociálne siete"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Navrhnuté pre vás"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Miniaplikácie <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> vpravo, vyhľadávanie a možnosti vľavo"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# miniaplikácia}few{# miniaplikácie}many{# widgets}other{# miniaplikácií}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# odkaz}few{# odkazy}many{# shortcuts}other{# odkazov}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Práca"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Konverzácie"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Zapisovanie poznámok"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Užitočné informácie poruke"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Ak chcete získavať informácie bez otvárania aplikácií, môžete si na plochu pridať miniaplikácie"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Pridať"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Pridať miniaplikáciu <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Klepnutím zmeňte nastavenia miniaplikácie"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Dobre"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Zmena nastavení miniaplikácie"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Hľadať aplikácie"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Načítavajú sa aplikácie…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Odinštalovať"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Info o aplikácii"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Inštalovať v súkromí"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Odinštalovať aplikáciu"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Inštalovať"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Nenavrhovať aplikáciu"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Pripnúť predpoveď"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Inštaluje sa <xliff:g id="NAME">%1$s</xliff:g>. Dokončené: <xliff:g id="PROGRESS">%2$s</xliff:g>."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Sťahuje sa aplikácia <xliff:g id="NAME">%1$s</xliff:g>. Stiahnuté: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Aplikácia <xliff:g id="NAME">%1$s</xliff:g> čaká na inštaláciu"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Aplikácia <xliff:g id="NAME">%1$s</xliff:g> je archivovaná. Stiahnite klepnutím."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Aplikácia <xliff:g id="NAME">%1$s</xliff:g> je archivovaná. Klepnutím ju stiahnite a obnovte."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Vyžaduje sa aktualizácia aplikácie"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Aplikácia, ktorú zastupuje táto ikona, nie je aktualizovaná. Môžete ju ručne aktualizovať, aby odkaz znova fungoval, prípadne môžete ikonu odstrániť."</string>
<string name="dialog_update" msgid="2178028071796141234">"Aktualizovať"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrujte"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Zlyhalo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Súkromný priestor"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Ponechajte súkromné aplikácie uzamknuté a skryté"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Klepnutím nastavte alebo otvorte"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Súkromné"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Nastavenia súkromného priestoru"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Súkromný priestor zamykania a odomykania"</string>
- <string name="ps_container_lock_title" msgid="2640257399982364682">"Uzamknutie"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Súkromné, odomknuté."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Súkromné, uzamknuté."</string>
+ <string name="ps_container_lock_title" msgid="2640257399982364682">"Uzamknúť"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Prechod súkromného priestoru"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Inštalovať aplikácie"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Inštalovať"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Inštalácia aplikácií v súkromnom priestore"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Rozšírená ponuka"</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 9472e44..5d423ca 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Pripomočki so onemogočeni v varnem načinu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Bližnjica ni na voljo"</string>
<string name="home_screen" msgid="5629429142036709174">"Začetni zaslon"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nastavitev zaganjalnika <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kot privzete aplikacije za začetni zaslon v nastavitvah"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Razdeljen zaslon"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Podatki o aplikaciji za: %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavitve uporabe za »%1$s«"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Shrani par aplikacij"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ta par aplikacij ni podprt v tej napravi"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Razprite napravo, če želite uporabljati ta par aplikacij"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Par aplikacij ni na voljo"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Pridržite pripomoček, da ga premaknete."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvakrat se dotaknite pripomočka in ga pridržite, da ga premaknete, ali pa uporabite dejanja po meri."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Širina %1$d, višina %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Pripomoček <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Pripomoček »<xliff:g id="WIDGET_NAME">%1$s</xliff:g>«, širina: %2$d, višina: %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Dotaknite se pripomočka in ga pridržite, če ga želite premikati po začetnem zaslonu."</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Dodaj na začetni zaslon"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Pripomoček »<xliff:g id="WIDGET_NAME">%1$s</xliff:g>« je dodan na začetni zaslon."</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Predlogi"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Vaše dnevne potrebščine"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Novice za vas"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Vaš kotiček za sprostitev"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Dosegajte cilje glede telesne pripravljenosti"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Bodite na tekočem z vremenom"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Morda vam bo všeč tudi"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Osnove"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Novice in revije"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Razvedrilo"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Družbeno"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Predlagano za vas"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Pripomočki <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> na desni, iskanje in možnosti na levi"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# pripomoček}one{# pripomoček}two{# pripomočka}few{# pripomočki}other{# pripomočkov}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# bližnjica}one{# bližnjica}two{# bližnjici}few{# bližnjice}other{# bližnjic}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Služba"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Pogovori"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Ustvarjanje zapiskov"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Koristne informacije na dosegu prstov"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Če si želite podatke ogledati brez odpiranja aplikacij, lahko na začetni zaslon dodate pripomočke."</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodajanje pripomočka »<xliff:g id="WIDGET_NAME">%1$s</xliff:g>«"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Dotaknite se, če želite spremeniti nastavitve pripomočka."</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Razumem"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Spreminjanje nastavitev pripomočka"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Iskanje programov"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Nalaganje aplikacij …"</string>
@@ -84,8 +86,9 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Odmesti"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Podatki o aplikaciji"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Namesti v zasebno"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Odmesti aplikacijo"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Namesti"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne predlagaj aplikacij"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne predlagaj"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Predvidevanje pripenjanja"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"namestitev bližnjic"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Aplikaciji dovoli dodajanje bližnjic brez posredovanja uporabnika."</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> se namešča, dokončano: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Prenašanje aplikacije <xliff:g id="NAME">%1$s</xliff:g>; preneseno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Aplikacija <xliff:g id="NAME">%1$s</xliff:g> čaka na namestitev"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Aplikacija <xliff:g id="NAME">%1$s</xliff:g> je arhivirana. Dotaknite se za prenos."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Aplikacija <xliff:g id="NAME">%1$s</xliff:g> je arhivirana. Dotaknite se za prenos in obnovitev."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Zahtevana je posodobitev aplikacije"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Aplikacija za to ikono ni posodobljena. Lahko jo ročno posodobite, da znova omogočite to bližnjico, ali pa odstranite ikono."</string>
<string name="dialog_update" msgid="2178028071796141234">"Posodobi"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtriranje"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Ni uspelo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Zasebni prostor"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Naj vaše zasebne aplikacije ostanejo zaklenjene in skrite"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Dotaknite se, da ga nastavite ali odprete"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Zasebno"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Nastavitve zasebnega prostora"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Zaklepanje/odklepanje zasebnega prostora"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Zasebno, odklenjeno."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Zasebno, zaklenjeno."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Zaklepanje"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Preklapljanje zasebnega prostora"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Nameščanje aplikacij"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Namestitev"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Nameščanje aplikacij v zasebni prostor"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Oblaček z dodatnimi elementi"</string>
</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index bfc6fb3..8f4133d 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikacionet janë të çaktivizuara në modalitetin e sigurt"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shkurtorja nuk është e disponueshme"</string>
<string name="home_screen" msgid="5629429142036709174">"Ekrani bazë"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Cakto <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> si aplikacionin e parazgjedhur të ekranit bazë te \"Cilësimet\""</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekrani i ndarë"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacioni i aplikacionit për %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Cilësimet e përdorimit për \"%1$s\""</string>
<string name="save_app_pair" msgid="5647523853662686243">"Ruaj çiftin e aplikacioneve"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ky çift aplikacionesh nuk mbështetet në këtë pajisje"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Shpalose pajisjen për të përdorur këtë çift aplikacionesh"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Çifti i aplikacioneve nuk ofrohet"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Prek dhe mbaj shtypur një miniaplikacion për ta zhvendosur."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Trokit dy herë dhe mbaje shtypur një miniapliikacion për ta zhvendosur atë ose për të përdorur veprimet e personalizuara."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d i gjerë me %2$d i lartë"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> miniaplikacion"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Miniaplikacioni <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, me gjerësi %2$d dhe lartësi %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Prek dhe mbaj të shtypur miniaplikacionin për ta lëvizur atë nëpër ekranin bazë"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Shto në ekranin bazë"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Miniaplikacioni <xliff:g id="WIDGET_NAME">%1$s</xliff:g> u shtua në ekranin bazë"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Sugjerime"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Aplikacionet thelbësore të përditshme"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Lajme për ty"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Zona jote e qetësisë"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Realizo objektivat e stërvitjes"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Qëndro i informuar për motin"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Gjithashtu mund të të pëlqejë"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Thelbësoret"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Lajme dhe revista"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Argëtim"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Rrjetet sociale"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sugjeruar për ty"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Miniaplikacionet e <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> në të djathtë, kërkimi dhe opsionet në të majtë"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# miniaplikacion}other{# miniaplikacione}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# shkurtore}other{# shkurtore}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Puna"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Bisedat"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Mbajtja e shënimeve"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Informacione të dobishme në majë të gishtave të tu"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Për të marrë informacione pa i hapur aplikacionet, mund të shtosh miniaplikacione në ekranin bazë"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Shto"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Shto miniaplikacionin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Trokit për të ndryshuar cilësimet e miniaplikacionit"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"E kuptova"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Ndrysho cilësimet e miniaplikacionit"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Kërko për aplikacione"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Po ngarkon aplikacionet..."</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Çinstalo"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Info mbi aplikacionin"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instalo në private"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Çinstalo aplikacionin"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Instalo"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Mos sugjero aplikacion"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Gozhdo parashikimin"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> po instalohet, <xliff:g id="PROGRESS">%2$s</xliff:g> i përfunduar"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> po shkarkohet, <xliff:g id="PROGRESS">%2$s</xliff:g> të përfunduara"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> po pret të instalohet"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> është arkivuar. Trokit për të shkarkuar."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> është arkivuar. Trokit për ta shkarkuar dhe restauruar."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Kërkohet përditësimi i aplikacionit"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Aplikacioni për këtë ikonë nuk është përditësuar. Mund ta përditësosh manualisht për të riaktivizuar këtë shkurtore ose hiq ikonën."</string>
<string name="dialog_update" msgid="2178028071796141234">"Përditëso"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtro"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Dështoi: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Hapësira private"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Mbaji të kyçura dhe të fshehura aplikacionet private"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Trokit për të konfiguruar ose për të hapur"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Private"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Cilësimet e \"Hapësirës private\""</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Kyç/Shkyç \"Hapësirën private\""</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Private, e shkyçur."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Private, e kyçur."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Kyç"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Kalimi te \"Hapësira private\""</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Instalo aplikacionet"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Instalo"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalo aplikacionet në hapësirën private"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Tejkalimi"</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 03eb603..cd6523c 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Подељени екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Информације о апликацији за: %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Подешавања потрошње за %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Сачувај пар апликација"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Овај пар апликација није подржан на овом уређају"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Додали сте виџет <xliff:g id="WIDGET_NAME">%1$s</xliff:g> на почетни екран"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Предлози"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Основни виџети за сваки дан"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Вести за вас"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Зона за опуштање"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Остварите фитнес циљеве"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Будите у току са временским приликама"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Можда ће вам се допасти и"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Основно"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Новости и часописи"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Забава"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Друштвене мреже"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Предложено за вас"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Виџети <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> са десне стране, претрага и опције са леве стране"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# виџет}one{# виџет}few{# виџета}other{# виџета}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# пречица}one{# пречица}few{# пречице}other{# пречица}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Посао"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Конверзације"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Прављење бележака"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Корисне информације надохват руке"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Да бисте пронашли информације без отварања апликација, можете да додате виџете на почетни екран"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Додај"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Додајте виџет <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Додирните да бисте променили подешавања виџета"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Важи"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Деинсталирај"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Подаци о апликацији"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Инсталирај на приватни"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Деинсталирајте апликацију"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Инсталирај"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Не предлажи апликацију"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Закачи предвиђање"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> се инсталира, <xliff:g id="PROGRESS">%2$s</xliff:g> готово"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> се преузима, завршено је <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> чека на инсталирање"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Апликација <xliff:g id="NAME">%1$s</xliff:g> је архивирана. Додирните да бисте је преузели."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Апликација <xliff:g id="NAME">%1$s</xliff:g> је архивирана. Додирните да бисте је преузели и вратили."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Треба да ажурирате апликацију"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Апликација за ову икону није ажурирана. Можете да је ручно ажурирате да бисте поново омогућили ову пречицу или уклоните икону."</string>
<string name="dialog_update" msgid="2178028071796141234">"Ажурирај"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Филтер"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Није успело: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Приватни простор"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Нека приватне апликације буду закључане и сакривене"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Додирните да бисте подесили или отворили"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Приватно"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Подешавања приватног простора"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Закључај/откључај приватни простор"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Приватно, откључано."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Приватно, закључано."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Закључавање"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Пренос приватног простора"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Инсталирајте апликације"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Инсталирајте"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Инсталирај апликације у приватан простор"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Преклопно"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index dab8407..4f95c67 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets är inaktiverade i felsäkert läge"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Genvägen är inte tillgänglig"</string>
<string name="home_screen" msgid="5629429142036709174">"Startskärm"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ställ in <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> som standardstartskärmsapp i Inställningar"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delad skärm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformation för %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Användningsinställningar för %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Spara app-par"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"De här apparna som ska användas tillsammans stöds inte på den här enheten"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Vik upp enheten för att använda de här apparna som ska användas tillsammans"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"App-paret är inte tillgängligt"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Tryck länge för att flytta en widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Tryck snabbt två gånger och håll kvar för att flytta en widget eller använda anpassade åtgärder."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d bred gånger %2$d hög"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget för <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widgeten <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d i bredd och %3$d i höjd"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Tryck länge på widgeten om du vill flytta den på startskärmen"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Lägg till på startskärmen"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget för <xliff:g id="WIDGET_NAME">%1$s</xliff:g> har lagts till på startskärmen"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Förslag"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Det viktigaste i vardagen"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Nyheter för dig"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Koppla av"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Nå dina träningsmål"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Håll koll på vädret"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Andra appar du kanske gillar"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Viktigt"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Nyheter och tidskrifter"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Underhållning"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Socialt"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Våra förslag"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Widgetar för <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> till höger, sökning och alternativ till vänster"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgetar}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# genväg}other{# genvägar}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Arbete"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Konversationer"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Anteckna"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Användbar information nära till hands"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Om du vill ha information utan att öppna appar kan du lägga till widgetar på startskärmen"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Lägg till"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Lägg till widgeten <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Tryck för att ändra inställningarna för widgeten"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Ändra inställningarna för widgeten"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Sök efter appar"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Läser in appar …"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Avinstallera"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Info om appen"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Installera i privat"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Avinstallera appen"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Installera"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Föreslå inte app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Fäst förslag"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installeras. <xliff:g id="PROGRESS">%2$s</xliff:g> har slutförts"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> laddas ned, <xliff:g id="PROGRESS">%2$s</xliff:g> klart"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> väntar på installation"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> har arkiverats. Tryck för att ladda ned."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> har arkiverats. Tryck för att ladda ner och återställa."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Du måste uppdatera appen"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Appen för den här ikonen har inte uppdaterats. Du kan uppdatera den manuellt för att återaktivera genvägen eller ta bort ikonen."</string>
<string name="dialog_update" msgid="2178028071796141234">"Uppdatera"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Misslyckades: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privat rum"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Håll privata appar låsta och dolda"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Tryck för att ställa in eller öppna"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privat"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Inställningar för privat rum"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lås eller lås upp ditt privata rum"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privat, olåst."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privat, låst."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Lås"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Överföring av privat rum"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Installera appar"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Installera"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Installera appar i privat rum"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Fler alternativ"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 193c92e..53a9abe 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Wijeti zimezimwa katika hali ya Usalama"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Hakuna njia ya mkato"</string>
<string name="home_screen" msgid="5629429142036709174">"Skrini ya kwanza"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Weka <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> iwe programu chaguomsingi ya mwanzo kwenye Mipangilio"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gawa skrini"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Maelezo ya programu ya %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Mipangilio ya matumizi ya %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Hifadhi jozi ya programu"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Jozi hii ya programu haitumiki kwenye kifaa hiki"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Fungua kifaa ili utumie jozi hii ya programu"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Kipengele cha jozi ya programu hakipatikani"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Gusa na ushikilie ili usogeze wijeti."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Gusa mara mbili na ushikilie ili usogeze wijeti au utumie vitendo maalum."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Upana wa %1$d na kimo cha %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Wijeti ya <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Wijeti ya <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, upana wa %2$d kwa urefu wa %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Gusa na ushikilie wijeti ili uisogeze kwenye skrini ya kwanza"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Weka kwenye skrini ya kwanza"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Umeongeza wijeti ya <xliff:g id="WIDGET_NAME">%1$s</xliff:g> kwenye skrini ya kwanza"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Mapendekezo"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Hati Zako Muhimu za Kila Siku"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Habari Kwa Ajili Yako"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Mahali Pako pa Kupumzika"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Fikia Malengo Yako ya Siha"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Pata Taarifa kuhusu Hali ya Hewa"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Huenda Pia Ukapenda"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Vya msingi"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Habari na magazeti"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Burudani"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Mitandao ya kijamii"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Unayopendekezewa"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Wijeti za <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ziko upande wa kulia, utafutaji na chaguo ziko upande wa kushoto"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{Wijeti #}other{Wijeti #}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{Njia # ya mkato}other{Njia # za mkato}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Kazini"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Mazungumzo"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Kuandika madokezo"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Maelezo muhimu, popote ulipo"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Ili upate maelezo bila kufungua programu, unaweza kuweka wijeti kwenye skrini yako ya kwanza"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Weka"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Weka wijeti ya <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Gusa ili ubadilishe mipangilio ya wijeti"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Nimeelewa"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Badilisha mipangilio ya wijeti"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Tafuta programu"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Inapakia programu..."</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Ondoa"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Maelezo ya programu"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Sakinisha faraghani"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Ondoa programu"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Sakinisha"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Isipendekeze programu"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Bandika Utabiri"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Inasakinisha <xliff:g id="NAME">%1$s</xliff:g>, imekamilika <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> inapakuliwa, <xliff:g id="PROGRESS">%2$s</xliff:g> imekamilika"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> inasubiri kusakinisha"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> imewekwa kwenye kumbukumbu. Gusa ili uipakue."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> imewekwa kwenye kumbukumbu. Gusa ili upakue na urejeshe."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Unahitaji kusasisha programu"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Programu ya aikoni hii haijasasishwa. Unaweza kusasisha mwenyewe ili uruhusu upya njia hii ya mkato au uondoe aikoni."</string>
<string name="dialog_update" msgid="2178028071796141234">"Sasisha"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Kichujio"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Hitilafu: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Nafasi ya faragha"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Funga na ufiche programu za faragha"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Gusa uweke mipangilio au ufungue"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Faragha"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Mipangilio ya Nafasi ya Faragha"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Funga/Fungua Nafasi ya Faragha"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Ya faragha, imefunguliwa."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Ya faragha, imefungwa."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Funga"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Mabadiliko ya Nafasi ya Faragha"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Sakinisha programu"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Weka"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Sakinisha programu kwenye Sehemu ya Faragha"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Menyu ya vipengee vya ziada"</string>
</resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index cc1f09e..eda9b2d 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -40,6 +40,7 @@
<!-- Hotseat -->
<dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
<dimen name="spring_loaded_hotseat_top_margin">65dp</dimen>
+ <dimen name="min_hotseat_icon_space">17dp</dimen>
<!-- Dragging -->
<dimen name="drop_target_top_margin">64dp</dimen>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index 3c79588..27aba6b 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -37,6 +37,7 @@
<!-- Widget picker-->
<dimen name="widget_list_horizontal_margin">30dp</dimen>
+ <dimen name="widget_cell_horizontal_padding">16dp</dimen>
<!-- Folder spaces -->
<dimen name="folder_footer_horiz_padding">24dp</dimen>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 2f79f40..738f85c 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"திரைப் பிரிப்பு"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sக்கான ஆப்ஸ் தகவல்கள்"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sக்கான உபயோக அமைப்புகள்"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ஆப்ஸ் ஜோடியைச் சேமி"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"இந்தச் சாதனத்தில் இந்த ஆப்ஸ் ஜோடி ஆதரிக்கப்படவில்லை"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> விட்ஜெட் முகப்புத் திரையில் சேர்க்கப்பட்டது"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"பரிந்துரைகள்"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"உங்கள் தினசரி அத்தியாவசியத் தேவைகள்"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"உங்களுக்கான செய்திகள்"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"உங்கள் மனதுக்கு இதமானவை"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"உடற்பயிற்சி இலக்குகளை அடையுங்கள்"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"வானிலை குறித்து முன்கூட்டியே அறிந்திருங்கள்"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"நீங்கள் இவற்றையும் விரும்பக்கூடும்"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"அத்தியாவசியமானவை"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"செய்திகள் & பத்திரிக்கைகள்"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"பொழுதுபோக்கு"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"சமூகம்"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"உங்களுக்கான பரிந்துரைகள்"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> விட்ஜெட்கள் வலதுபுறத்தில் உள்ளன, தேடல் மற்றும் விருப்பங்கள் இடதுபுறத்தில் உள்ளன"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# விட்ஜெட்}other{# விட்ஜெட்டுகள்}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# ஷார்ட்கட்}other{# ஷார்ட்கட்கள்}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"பணி"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"உரையாடல்கள்"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"குறிப்பெடுத்தல்"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"விரல்நுனியில் பயனுள்ள தகவல்களைப் பெறுங்கள்"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"முகப்புத் திரையில் விட்ஜெட்டுகளைச் சேர்த்து ஆப்ஸைத் திறக்காமலேயே தகவல்களைப் பெறலாம்"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"சேர்"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> விட்ஜெட்டைச் சேர்க்கும்"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"விட்ஜெட் அமைப்புகளை மாற்றத் தட்டவும்"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"சரி"</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>
@@ -84,9 +86,10 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"நிறுவல் நீக்கு"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ஆப்ஸ் தகவல்"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"தனிப்பட்டதில் நிறுவு"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ஆப்ஸை நிறுவல் நீக்கு"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"நிறுவு"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"பரிந்துரைக்காதே"</string>
- <string name="pin_prediction" msgid="4196423321649756498">"கணிக்கப்பட்ட ஆப்ஸைப் பின் செய்தல்"</string>
+ <string name="pin_prediction" msgid="4196423321649756498">"கணிக்கப்பட்டதைப் பின் செய்"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"குறுக்குவழிகளை நிறுவுதல்"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"பயனரின் அனுமதி இல்லாமல் குறுக்குவழிகளைச் சேர்க்கப் ஆப்ஸை அனுமதிக்கிறது."</string>
<string name="permlab_read_settings" msgid="5136500343007704955">"முகப்புத் திரையின் அமைப்புகளையும் ஷார்ட்கட்களையும் படித்தல்"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> நிறுவப்படுகிறது, <xliff:g id="PROGRESS">%2$s</xliff:g> முடிந்தது"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>ஐப் பதிவிறக்குகிறது, <xliff:g id="PROGRESS">%2$s</xliff:g> முடிந்தது"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>ஐ நிறுவுவதற்காகக் காத்திருக்கிறது"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> காப்பிடப்பட்டுள்ளது. பதிவிறக்க தட்டுங்கள்."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> காப்பிடப்பட்டுள்ளது. அதைப் பதிவிறக்கி மீட்டெடுக்க தட்டுங்கள்."</string>
<string name="dialog_update_title" msgid="114234265740994042">"ஆப்ஸைப் புதுப்பியுங்கள்"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"இந்த ஐகானுக்கான ஆப்ஸ் புதுப்பிக்கப்படவில்லை. இந்த ஷார்ட்கட்டை மீண்டும் இயக்கவோ ஐகானை அகற்றவோ நீங்களாகவே புதுப்பிக்கலாம்."</string>
<string name="dialog_update" msgid="2178028071796141234">"புதுப்பி"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"வடிப்பான்"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"தோல்வி: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"தனிப்பட்ட சேமிப்பிடம்"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"தனிப்பட்ட ஆப்ஸை லாக் செய்தும் மறைத்தும் வைக்கலாம்"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"அமைக்கவோ திறக்கவோ தட்டுங்கள்"</string>
<string name="ps_container_title" msgid="4391796149519594205">"தனிப்பட்டது"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"தனிப்பட்ட சேமிப்பிட அமைப்புகள்"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"தனிப்பட்ட சேமிப்பிடத்தை லாக்/அன்லாக் செய்யும்"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"தனிப்பட்டது, அன்லாக் செய்யப்பட்டுள்ளது."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"தனிப்பட்டது, லாக் செய்யப்பட்டுள்ளது."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"பூட்டு"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"தனிப்பட்ட சேமிப்பிடத்திற்கு மாற்றுகிறது"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ஆப்ஸை நிறுவுதல்"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"நிறுவுக"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"தனிப்பட்ட சேமிப்பிடத்தில் ஆப்ஸை நிறுவும்"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"கூடுதல் விருப்பங்களைக் காட்டும்"</string>
</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 5f104e2..0520ebf 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"స్ప్లిట్ స్క్రీన్"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s కోసం యాప్ సమాచారం"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sకు సంబంధించిన వినియోగ సెట్టింగ్లు"</string>
<string name="save_app_pair" msgid="5647523853662686243">"యాప్ పెయిర్ను సేవ్ చేయండి"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ఈ పరికరంలో ఈ యాప్ పెయిర్ సపోర్ట్ చేయదు"</string>
<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="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d వెడల్పు X %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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"మొదటి స్క్రీన్కు <xliff:g id="WIDGET_NAME">%1$s</xliff:g> విడ్జెట్ జోడించబడింది"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"సూచనలు"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"మీ రోజువారీ అవసరాలు"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"మీ కోసం వార్తలు"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"మీరు ప్రశాంతంగా ఉండే ప్రదేశం"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"ఫిట్నెస్ లక్ష్యాలను చేరుకోండి"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"వాతావరణాన్ని ముందుగానే తెలుసుకోండి"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"మీరు వీటిని కూడా ఇష్టపడవచ్చు"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"నిత్యావసరాలు"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"వార్తలు & మ్యాగజైన్లు"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"వినోదం"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"సామాజికం"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"మీ కోసం సూచించినవి"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"కుడి వైపున <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> విడ్జెట్లు, ఎడమ వైపున సెర్చ్, ఇతర ఆప్షన్లు"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# విడ్జెట్}other{# విడ్జెట్లు}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# షార్ట్కట్}other{# షార్ట్కట్లు}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ఆఫీస్"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"సంభాషణలు"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"నోట్-టేకింగ్"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"మీ చేతివేళ్ల మీద ఉపయోగకరమైన సమాచారం"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"యాప్లను తెరవకుండా సమాచారాన్ని పొందడానికి, మీరు మీ మొదటి స్క్రీన్కు విడ్జెట్లను జోడించవచ్చు"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"జోడించండి"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> విడ్జెట్ను జోడించండి"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"విడ్జెట్ సెట్టింగ్లను మార్చడానికి ట్యాప్ చేయండి"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"అర్థమైంది"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"అన్ఇన్స్టాల్ చేయండి"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"యాప్ సమాచారం"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ప్రైవేట్ ఇన్స్టాల్"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"యాప్ను అన్ఇన్స్టాల్ చేయండి"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ఇన్స్టాల్ చేయండి"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"యాప్ సూచించకు"</string>
<string name="pin_prediction" msgid="4196423321649756498">"సూచనను పిన్ చేయండి"</string>
@@ -123,7 +126,7 @@
<string name="title_missing_notification_access" msgid="7503287056163941064">"నోటిఫికేషన్ యాక్సెస్ అవసరం"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"నోటిఫికేషన్ డాట్లను చూపించడానికి <xliff:g id="NAME">%1$s</xliff:g>కు యాప్ నోటిఫికేషన్లను ఆన్ చేయండి"</string>
<string name="title_change_settings" msgid="1376365968844349552">"సెట్టింగ్లను మార్చు"</string>
- <string name="notification_dots_service_title" msgid="4284221181793592871">"నోటిఫికేషన్ డాట్లను చూపు"</string>
+ <string name="notification_dots_service_title" msgid="4284221181793592871">"నోటిఫికేషన్ డాట్లను చూపండి"</string>
<string name="developer_options_title" msgid="700788437593726194">"డెవలపర్ ఆప్షన్లు"</string>
<string name="auto_add_shortcuts_label" msgid="4926805029653694105">"యాప్ చిహ్నాలను మొదటి స్క్రీన్కు జోడించండి"</string>
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"కొత్త యాప్ల కోసం"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g>ను ఇన్స్టాల్ చేయడం, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> డౌన్లోడ్ అవుతోంది, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ఇన్స్టాల్ కావడానికి వేచి ఉంది"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> ఆర్కైవ్ చేయబడింది. డౌన్లోడ్ చేయడానికి ట్యాప్ చేయండి."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> ఆర్కైవ్ చేయబడింది. డౌన్లోడ్ చేయడానికి, రీస్టోర్ చేయడానికి ట్యాప్ చేయండి."</string>
<string name="dialog_update_title" msgid="114234265740994042">"యాప్ను అప్డేట్ చేయడం అవసరం"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"ఈ చిహ్నం కోసం యాప్ అప్డేట్ చేయబడలేదు. మీరు ఈ షార్ట్కట్ను మళ్లీ ఎనేబుల్ చేయడానికి మాన్యువల్గా అప్డేట్ చేయవచ్చు లేదా చిహ్నాన్ని తీసివేయవచ్చు."</string>
<string name="dialog_update" msgid="2178028071796141234">"అప్డేట్ చేయండి"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ఫిల్టర్ చేయి"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"విఫలమైంది: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"ప్రైవేట్ స్పేస్"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"ప్రైవేట్ యాప్లను లాక్ చేసి దాచి ఉంచండి"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"సెటప్ చేయడానికి లేదా తెరవడానికి ట్యాప్ చేయండి"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ప్రైవేట్"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"ప్రైవేట్ స్పేస్ సెట్టింగ్లు"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"ప్రైవేట్ స్పేస్ను లాక్/అన్లాక్ చేయండి"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ప్రైవేట్, అన్లాక్ చేయబడింది."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ప్రైవేట్, లాక్ చేయబడింది."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"లాక్ చేయండి"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"ప్రైవేట్ స్పేస్ కేటాయించడం జరుగుతుంది"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"యాప్లను ఇన్స్టాల్ చేయండి"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ఇన్స్టాల్ చేయండి"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"ప్రైవేట్ స్పేస్కు యాప్లను ఇన్స్టాల్ చేయండి"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"ఓవర్ఫ్లో"</string>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index c3c2c20..554fd94 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"แยกหน้าจอ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"ข้อมูลแอปสำหรับ %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"การตั้งค่าการใช้งานสำหรับ %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"บันทึกคู่แอป"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ไม่รองรับคู่แอปนี้ในอุปกรณ์เครื่องนี้"</string>
<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="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"กว้าง %1$d x สูง %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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"เพิ่มวิดเจ็ต <xliff:g id="WIDGET_NAME">%1$s</xliff:g> ลงในหน้าจอหลักแล้ว"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"คำแนะนำ"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"สิ่งจำเป็นในชีวิตประจำวันของคุณ"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"ข่าวสารสำหรับคุณ"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"พื้นที่สบายๆ ของคุณ"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"บรรลุเป้าหมายการออกกำลังกาย"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"รู้สภาพอากาศล่วงหน้า"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"คุณอาจชอบ"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"รายการที่ห้ามพลาด"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ข่าวสารและนิตยสาร"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"ความบันเทิง"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"โซเชียล"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"แนะนำให้คุณ"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"วิดเจ็ต<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>ทางด้านขวา การค้นหาและตัวเลือกทางด้านซ้าย"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{วิดเจ็ต # รายการ}other{วิดเจ็ต # รายการ}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{ทางลัด # รายการ}other{ทางลัด # รายการ}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"งาน"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"การสนทนา"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"การจดบันทึก"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"เข้าถึงข้อมูลที่เป็นประโยชน์ได้จากปลายนิ้ว"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"หากต้องการรับข้อมูลโดยไม่เปิดแอป ให้เพิ่มวิดเจ็ตลงในหน้าจอหลัก"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"เพิ่ม"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"เพิ่มวิดเจ็ต <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"แตะเพื่อเปลี่ยนการตั้งค่าวิดเจ็ต"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"รับทราบ"</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>
@@ -83,7 +85,8 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"นำออก"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"ถอนการติดตั้ง"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ข้อมูลแอป"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ติดตั้งในส่วนตัว"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ติดตั้งในแบบส่วนตัว"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ถอนการติดตั้งแอป"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ติดตั้ง"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ไม่ต้องแนะนำแอป"</string>
<string name="pin_prediction" msgid="4196423321649756498">"ปักหมุดแอปที่คาดการณ์ไว้"</string>
@@ -111,7 +114,7 @@
<string name="folder_name_format_exact" msgid="8626242716117004803">"โฟลเดอร์: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> รายการ"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"โฟลเดอร์: <xliff:g id="NAME">%1$s</xliff:g>, อย่างน้อย <xliff:g id="SIZE">%2$d</xliff:g> รายการ"</string>
<string name="app_pair_name_format" msgid="8134106404716224054">"คู่แอป: <xliff:g id="APP1">%1$s</xliff:g> และ <xliff:g id="APP2">%2$s</xliff:g>"</string>
- <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"วอลเปเปอร์และรูปแบบ"</string>
+ <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"วอลเปเปอร์และสไตล์"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"แก้ไขหน้าจอหลัก"</string>
<string name="settings_button_text" msgid="8873672322605444408">"การตั้งค่าหน้าจอหลัก"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"ปิดใช้โดยผู้ดูแลระบบ"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"กำลังติดตั้ง <xliff:g id="NAME">%1$s</xliff:g> เสร็จแล้ว <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"กำลังดาวน์โหลด <xliff:g id="NAME">%1$s</xliff:g> เสร็จแล้ว <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> กำลังรอติดตั้ง"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"เก็บถาวร <xliff:g id="NAME">%1$s</xliff:g> แล้ว แตะเพื่อดาวน์โหลด"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"เก็บถาวร <xliff:g id="NAME">%1$s</xliff:g> แล้ว แตะเพื่อดาวน์โหลดและกู้คืน"</string>
<string name="dialog_update_title" msgid="114234265740994042">"ต้องอัปเดตแอป"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"แอปสำหรับไอคอนนี้ยังไม่ได้อัปเดต คุณอัปเดตด้วยตนเองได้โดยเปิดใช้ทางลัดนี้อีกครั้งหรือนำไอคอนออก"</string>
<string name="dialog_update" msgid="2178028071796141234">"อัปเดต"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ตัวกรอง"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"ไม่สำเร็จ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"พื้นที่ส่วนตัว"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"ล็อกและซ่อนแอปส่วนตัวไว้"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"แตะเพื่อตั้งค่าหรือเปิด"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ส่วนตัว"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"การตั้งค่าพื้นที่ส่วนตัว"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"ล็อก/ปลดล็อกพื้นที่ส่วนตัว"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ส่วนตัว ปลดล็อกอยู่"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ส่วนตัว ล็อกอยู่"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ล็อก"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"การเปลี่ยนไปใช้พื้นที่ส่วนตัว"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ติดตั้งแอป"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"ติดตั้ง"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"ติดตั้งแอปไปยังพื้นที่ส่วนตัว"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"การดำเนินการเพิ่มเติม"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 16c9cb8..cb6fe66 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Naka-disable ang mga widget sa Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Hindi available ang shortcut"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Itakda ang <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> bilang default na home app sa Mga Setting"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Impormasyon ng app para sa %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Mga setting ng paggamit para sa %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"I-save ang app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Hindi sinusuportahan sa device na ito ang pares ng app na ito"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"I-unfold ang device para magamit ang pares ng app na ito"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Hindi available ang pares ng app"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Pindutin nang matagal para ilipat ang widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"I-double tap at pindutin nang matagal para ilipat ang widget o gumamit ng mga custom na pagkilos."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ang lapad at %2$d ang taas"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget na <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d ang lapad at %3$d ang taas"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Pindutin nang matagal ang widget para ilipat-lipat ito sa home screen"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Idagdag sa home screen"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Idinagdag sa home screen ang widget na <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Mga Suhestyon"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Ang Pang-araw-araw Mong Mga Essential"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Balita para sa Iyo"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Ang Iyong Chill Zone"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Makamit ang Iyong Mga Layunin sa Fitness"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Manatiling Handa sa Lagay ng Panahon"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Baka Magustuhan Mo Rin"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Mga essential"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Balita at mga magazine"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entertainment"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Social"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Iminumungkahi para sa iyo"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Mga widget ng <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> sa kanan, paghahanap at mga opsyon sa kaliwa"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}one{# widget}other{# na widget}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# shortcut}one{# shortcut}other{# na shortcut}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabaho"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Mga Pag-uusap"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Pagtatala"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Abot-kamay na mahalagang impormasyon"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Para makakuha ng impormasyon nang hindi nagbubukas ng mga app, puwede kang magdagdag ng mga widget sa iyong home screen"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Idagdag"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Idagdag ang widget na <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"I-tap para baguhin ang mga setting ng widget"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Baguhin ang mga setting ng widget"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Maghanap ng mga app"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Naglo-load ng mga app…"</string>
@@ -84,8 +86,9 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"I-uninstall"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Impormasyon ng app"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Pribadong i-install"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"I-uninstall ang app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"I-install"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Huwag magmungkahi ng app"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Huwag magmungkahi"</string>
<string name="pin_prediction" msgid="4196423321649756498">"I-pin ang Hula"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"i-install ang mga shortcut"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Pinapayagan ang isang app na magdagdag ng mga shortcut nang walang panghihimasok ng user."</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Ini-install ang <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> kumpleto"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Dina-download na ang <xliff:g id="NAME">%1$s</xliff:g>, tapos na ang <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Hinihintay nang mag-install ang <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Naka-archive ang <xliff:g id="NAME">%1$s</xliff:g>. I-tap para i-download."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Naka-archive ang <xliff:g id="NAME">%1$s</xliff:g>. I-tap para i-download at i-restore."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Kinakailangang i-update ang app"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Hindi updated ang app para sa icon na ito. Puwede kang manual na mag-update para ma-enable ulit ang shortcut na ito, o alisin ang icon."</string>
<string name="dialog_update" msgid="2178028071796141234">"I-update"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Hindi nagawa: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Pribadong space"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Panatilihing naka-lock at nakatago ang mga pribadong app"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"I-tap para i-set up o buksan"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Pribado"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Mga Setting ng Pribadong Space"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"I-lock/I-unlock ang Pribadong Space"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Pribado, naka-unlock."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Pribado, naka-lock."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"I-lock"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Pag-transition ng Pribadong Space"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Mag-install ng mga app"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"I-install"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Mag-install ng mga app sa Pribadong Space"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overflow"</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 7633940..b12ec27 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Güvenli modda widget\'lar devre dışı"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Kısayol kullanılamıyor"</string>
<string name="home_screen" msgid="5629429142036709174">"Ana ekran"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> başlatıcısını Ayarlar\'da varsayılan ana ekran uygulaması olarak ayarlayın"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Bölünmüş ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s uygulama bilgileri"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ile ilgili kullanım ayarları"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Uygulama çiftini kaydedin"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Bu uygulama çifti bu cihazda desteklenmiyor"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Bu uygulama çiftini kullanmak için katlanmış durumda olan cihazı açın"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Uygulama çifti kullanılamıyor"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Widget\'ı taşımak için dokunup basılı tutun."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Widget\'ı taşımak veya özel işlemleri kullanmak için iki kez dokunup basılı tutun."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"genişlik: %1$d, yükseklik: %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget\'ı"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget\'ı, %2$d genişlik x %3$d yükseklik"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Ana ekranda taşımak için widget\'a dokunup basılı tutun"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Ana ekrana ekle"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget\'ı ana ekrana eklendi"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Öneriler"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Günlük Gerekenler"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Size özel haberler"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Huzur alanınız"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Fitness hedeflerinize ulaşın"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Havanın durumu sizi şaşırtmasın"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Şunları da beğenebilirsiniz"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Önemliler"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Haberler ve dergiler"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Eğlence"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sosyal"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sizin için önerilenler"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> widget\'ları sağda, arama ve seçenekler solda"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widget}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# kısayol}other{# kısayol}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"İş"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Görüşmeler"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Not alma"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Faydalı bilgiler parmaklarınızın ucunda"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Uygulama açmadan bilgi almak için ana ekranınıza widget ekleyebilirsiniz"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Ekle"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget\'ı ekle"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Widget ayarlarını değiştirmek için dokunun"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Anladım"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Widget ayarlarını değiştir"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Uygulamalarda ara"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Uygulamalar yükleniyor…"</string>
@@ -83,9 +85,10 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"Sil"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Kaldır"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Uygulama bilgileri"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Özel olarak yükleyin"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Özel olarak yükle"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Uygulamayı kaldır"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Yükle"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Uygulama önerme"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Uygulamayı önerme"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Tahmini Sabitle"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"kısayolları yükle"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Uygulamaya, kullanıcı müdahalesi olmadan kısayol ekleme izni verir."</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> yükleniyor, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlandı"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> indiriliyor, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlandı"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> uygulaması yüklenmek için bekliyor"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> arşivlendi. İndirmek için dokunun."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> arşivlendi. İndirip geri yüklemek için dokunun."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Uygulama güncellemesi gerekli"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Bu simgenin uygulaması güncellenmemiş. Simgeyi kaldırabilir ya da uygulamayı manuel olarak güncelleyerek bu kısayolu yeniden etkinleştirebilirsiniz."</string>
<string name="dialog_update" msgid="2178028071796141234">"Güncelle"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtre"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Başarısız: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Gizli alan"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Özel uygulamaları kilitli ve gizli tutun"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Kurmak veya açmak için dokunun"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Gizli"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Gizli Alan Ayarları"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Gizli Alanı Kilitleyin/Kilidini Açın"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Gizli, kilidi açık."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Gizli, kilitli."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Kilit"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Gizli Alana Geçiş"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Uygulamaları yükleme"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Yükle"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Uygulamaları özel alana yükleyin"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Taşma"</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 7f56e1b..2a01dae 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Розділити екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Інформація про додаток для %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Параметри використання (%1$s)"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Зберегти пару додатків"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ці два додатки не можна одночасно використовувати на цьому пристрої"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Віджет <xliff:g id="WIDGET_NAME">%1$s</xliff:g> додано на головний екран"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Пропозиції"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Найнеобхідніше на кожен день"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Новини для вас"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Ваша зона розваг"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Досягайте своїх фітнес-цілей"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Завчасно дізнавайтеся про зміни погоди"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Вам також може сподобатися"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Основне"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Новини й журнали"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Розваги"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Соціальні мережі"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Пропозиції для вас"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>: віджети праворуч, пошук і опції ліворуч"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# віджет}one{# віджет}few{# віджети}many{# віджетів}other{# віджета}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# ярлик}one{# ярлик}few{# ярлики}many{# ярликів}other{# ярлика}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Робочі"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Розмови"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Створення нотаток"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Корисна інформація завжди під рукою"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Щоб отримувати інформацію, не відкриваючи додатки, ви можете додати на головний екран віджети"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Додати"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Додати віджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Натисніть, щоб змінити налаштування віджета"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Видалити додаток"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Про додаток"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Установити приватно"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Видалити додаток"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Установити"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Не пропонувати додаток"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Закріпити передбачений додаток"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> встановлюється, виконано <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> завантажується, <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> очікує на завантаження"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Додаток <xliff:g id="NAME">%1$s</xliff:g> заархівовано. Натисніть, щоб завантажити."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Додаток <xliff:g id="NAME">%1$s</xliff:g> заархівовано. Натисніть, щоб завантажити й відновити."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Потрібно оновити додаток"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Додаток для цього значка не оновлено. Ви можете оновити його вручну, щоб знову ввімкнути цю швидку команду, або можете вилучити значок."</string>
<string name="dialog_update" msgid="2178028071796141234">"Оновити"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Фільтр"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Не вдалося <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Приватний простір"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Зберігайте приватні додатки в прихованому й заблокованому сховищі"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Натисніть, щоб налаштувати чи відкрити"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Приватні"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Налаштування приватного простору"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Заблокувати/розблокувати приватний простір"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Приватний простір, розблоковано."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Приватний простір, заблоковано."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Заблокувати"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Перехід у приватний простір"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Установити додатки"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Установити"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Установити додатки в особистому просторі"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Додаткове меню"</string>
</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index c945bab..544357e 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"اسپلٹ اسکرین"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s کے لیے ایپ کی معلومات"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s کیلئے استعمال کی ترتیبات"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ایپس کے جوڑے کو محفوظ کریں"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ایپس کا یہ جوڑا اس آلے پر تعاون یافتہ نہیں ہے"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ویجیٹ کو ہوم اسکرین میں شامل کیا گیا"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"تجاویز"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"آپ کے روز مرہ کے لوازمات"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"آپ کے لیے خبریں"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"آپ کا آرام دہ زون"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"اپنی تندرستی کے مقاصد حاصل کریں"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"موسم سے باخبر رہیں"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"آپ کو یہ بھی پسند آ سکتا ہے"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"لوازمات"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"خبریں اور میگزینز"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"تفریح"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"سماجی"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"آپ کے لیے تجویز کردہ"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> دائیں طرف وجیٹس، بائیں طرف تلاش اور اختیارات"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# ویجیٹ}other{# ویجیٹس}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# شارٹ کٹ}other{# شارٹ کٹس}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"دفتری ویجیٹس"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"گفتگوئیں"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"نوٹ لکھنا"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"مفید معلومات کو آسانی سے حاصل کریں"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"ایپس کو کھولے بغیر معلومات حاصل کرنے کے لیے آپ اپنی ہوم اسکرین پر ویجیٹس شامل کر سکتے ہیں"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"شامل کریں"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ویجیٹ شامل کریں"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ویجیٹ ترتیبات تبدیل کرنے کے لیے تھپتھپائیں"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"سمجھ آ گئی"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"اَن انسٹال کریں"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ایپ کی معلومات"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"پرائیویٹ میں انسٹال کریں"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ایپ کو اَن انسٹال کریں"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"انسٹال کریں"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ایپ تجویز نہ کریں"</string>
<string name="pin_prediction" msgid="4196423321649756498">"پیشگوئی پن کریں"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> انسٹال کی جا رہی ہے، <xliff:g id="PROGRESS">%2$s</xliff:g> مکمل ہو گئی"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ڈاؤن لوڈ ہو رہا ہے، <xliff:g id="PROGRESS">%2$s</xliff:g> مکمل ہو گیا"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> انسٹال ہونے کا انتظار کر رہی ہے"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> کو آرکائیو کر لیا گیا ہے۔ ڈاؤن لوڈ کرنے کیلئے تھپتھپائیں۔"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> کو آرکائیو کر لیا گیا ہے۔ ڈاؤن لوڈ اور بحال کرنے کیلئے تھپتھپائیں۔"</string>
<string name="dialog_update_title" msgid="114234265740994042">"ایپ کی اپ ڈیٹ درکار ہے"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"اس آئیکن کیلئے ایپ کو اپ ڈیٹ نہیں کیا گیا ہے۔ آپ اس شارٹ کٹ کو دوبارہ فعال کرنے کے لیے دستی طور پر اپ ڈیٹ کر سکتے ہیں، یا آئیکن کو ہٹا سکتے ہیں۔"</string>
<string name="dialog_update" msgid="2178028071796141234">"اپ ڈیٹ کریں"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"فلٹر"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"ناکام ہو گيا: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"نجی اسپیس"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"نجی ایپس کو مقفل اور پوشیدہ رکھیں"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"سیٹ اپ کرنے یا کھولنے کے لیے تھپتھپائیں"</string>
<string name="ps_container_title" msgid="4391796149519594205">"نجی"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"نجی اسپیس کی ترتیبات"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"نجی اسپیس کو مقفل کریں/غیر مقفل کریں"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"نجی اسپیس غیر مقفل ہے۔"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"نجی اسپیس مقفل ہے۔"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"مقفل کریں"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"نجی اسپیس کی منتقلی"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"ایپس انسٹال کریں"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"انسٹال کریں"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"پرائیویٹ اسپیس میں ایپس انسٹال کریں"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"اوورفلو"</string>
</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 4fbacb7..5038d4f 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Xavfsiz rejimda vidjetlar o‘chirib qo‘yilgan"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Tezkor tugmadan foydalanib bo‘lmaydi"</string>
<string name="home_screen" msgid="5629429142036709174">"Bosh ekran"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Sozlamalar orqali <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ilovasini birlamchi bosh ekran ilovasi sifatida belgilash"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekranni ikkiga ajratish"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilovasi axboroti"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s uchun sarf sozlamalari"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Ilova juftini saqlash"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Bu ilova jufti ushbu qurilmada ishlamaydi"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Bu ilova juftidan foydalanish uchun qurilmani oching"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Ikkita ilovadan bir vaqtda foydalanish mumkin emas"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Vidjetni bosib turgan holatda suring."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Ikki marta bosib va bosib turgan holatda vidjetni tanlang yoki maxsus amaldan foydalaning."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Eni %1$d, bo‘yi %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ta vidjet"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidjeti, eniga: %2$d, boʻyiga: %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Bosh ekranda surish uchun vidjet ustiga bosib turing"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Bosh ekranga chiqarish"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidjeti bosh ekranga qoʻshildi"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Takliflar"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Kunlik muhim vazifalaringiz"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Siz uchun yangiliklar"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Sokin hududingiz"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Fitness maqsadlaringizga erishing"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Doim ob-havodan oldinda yuring"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Sizga yoqishi mumkin"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Asosiy ilovalar"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Yangiliklar va jurnallar"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Hordiq"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Ijtimoiy"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sizga tavsiya etiladi"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> vidjetlari oʻngda, qidiruv va sozlamalar chapda"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# ta vidjet}other{# ta vidjet}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# ta yorliq}other{# ta yorliq}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Ish"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Suhbatlar"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Qayd olish"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Barcha kerakli axborot doim yoningizda"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Kerakli ilovalarni ochmasdan turib ulardan axborot olish uchun vidjetlarni bosh ekranga chiqaring"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Chiqarish"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidjetini chiqarish"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Vidjet sozlamalarini oʻzgartirish uchun bosing"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"OK"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Vidjet sozlamalarini oʻzgartirish"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Ilovalarni qidirish"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Ilovalar yuklanmoqda…"</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"O‘chirib tashlash"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Ilova haqida"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Maxfiy oʻrnatish"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Ilovani oʻchirish"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"O‘rnatish"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Tavsiya qilinmasin"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Tavsiyani mahkamlash"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> oʻrnatlmoqda, <xliff:g id="PROGRESS">%2$s</xliff:g> yakunlandi"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> yuklab olinmoqda, <xliff:g id="PROGRESS">%2$s</xliff:g> bajarildi"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ilovasi o‘rnatilishi kutilmoqda"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"<xliff:g id="NAME">%1$s</xliff:g> arxivlangan. Yuklab olish uchun bosing."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> arxivlangan. Yuklab olish va tiklash uchun bosing."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Ilovani yangilash zarur"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Bu belgi uchun ilova yangilanmagan. Ushbu yorliqni qayta yoqish uchun oddiy usulda yangilashingiz yoki belgini olib tashlashingiz mumkin."</string>
<string name="dialog_update" msgid="2178028071796141234">"Yangilash"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Saralash"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Xato: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Shaxsiy xona"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Maxfiy ilovalar qulflangan va yashirin saqlansin"</string>
- <string name="ps_container_title" msgid="4391796149519594205">"Yopiq"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Sozlash yoki ochish uchun bosing"</string>
+ <string name="ps_container_title" msgid="4391796149519594205">"Maxfiy"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Shaxsiy xona sozlamalari"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Shaxsiy xonani ochish/qulflash"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Shaxsiy, ochildi."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Shaxsiy, qulflandi."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Qulflash"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Maxfiy joyga almashtirish"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Ilovalar oʻrnatish"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Oʻrnatish"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Ilovalarni Maxfiy makonga oʻrnatish"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Kengaytirish"</string>
</resources>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index e2f610e..fa87221 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -47,8 +47,8 @@
<color name="wallpaper_popup_scrim">@android:color/system_neutral1_900</color>
- <color name="folder_pagination_color_light">@android:color/system_accent1_600</color>
- <color name="folder_pagination_color_dark">@android:color/system_accent1_200</color>
+ <color name="page_indicator_dot_color_light">@android:color/system_accent1_600</color>
+ <color name="page_indicator_dot_color_dark">@android:color/system_accent1_200</color>
<color name="home_settings_header_accent">@android:color/system_accent1_600</color>
<color name="home_settings_header_collapsed">@android:color/system_neutral1_100</color>
@@ -97,6 +97,10 @@
@android:color/system_neutral2_700</color>
<color name="widget_picker_collapse_handle_color_light">
@android:color/system_neutral2_200</color>
+ <color name="widget_picker_add_button_background_color_light">
+ @android:color/system_accent1_600</color>
+ <color name="widget_picker_add_button_text_color_light">
+ @android:color/system_accent1_0</color>
<color name="work_fab_bg_color">
@android:color/system_accent1_200</color>
diff --git a/res/values-v34/colors.xml b/res/values-v34/colors.xml
new file mode 100644
index 0000000..26d3712
--- /dev/null
+++ b/res/values-v34/colors.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <color name="widget_picker_secondary_surface_color_light">
+ @android:color/system_surface_bright_light</color>
+ <color name="widget_picker_header_app_title_color_light">
+ @android:color/system_on_surface_light</color>
+ <color name="widget_picker_header_app_subtitle_color_light">
+ @android:color/system_on_surface_variant_light</color>
+ <color name="widget_cell_title_color_light">
+ @android:color/system_on_surface_light</color>
+ <color name="widget_cell_subtitle_color_light">
+ @android:color/system_on_surface_variant_light</color>
+</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 290adac..471fedd 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Tiện ích bị vô hiệu hóa ở chế độ an toàn"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lối tắt không khả dụng"</string>
<string name="home_screen" msgid="5629429142036709174">"Màn hình chính"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Đặt <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> làm trình chạy mặc định trong phần Cài đặt"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Chia đôi màn hình"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Thông tin ứng dụng cho %1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Chế độ cài đặt mức sử dụng %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Lưu cặp ứng dụng"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Cặp ứng dụng này không hoạt động được trên thiết bị này"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Hãy mở thiết bị để dùng cặp ứng dụng này"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Hiện không có cặp ứng dụng này"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Chạm và giữ để di chuyển một tiện ích."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Nhấn đúp và giữ để di chuyển một tiện ích hoặc sử dụng các thao tác tùy chỉnh."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Rộng %1$d x cao %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Tiện ích <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Tiện ích <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, rộng %2$d x cao %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Chạm và giữ tiện ích để di chuyển tiện ích đó xung quanh màn hình chính"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Thêm vào màn hình chính"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Đã thêm tiện ích <xliff:g id="WIDGET_NAME">%1$s</xliff:g> vào màn hình chính"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Nội dung đề xuất"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Các tiện ích thiết yếu hằng ngày của bạn"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Tin tức cho bạn"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Giai điệu thư giãn của bạn"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Đạt được mục tiêu tập thể dục"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Luôn nắm bắt tình hình thời tiết"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Có thể bạn cũng thích"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Các tiện ích thiết yếu"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Tin tức và tạp chí"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Giải trí"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Mạng xã hội"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Đề xuất cho bạn"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Tiện ích <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> ở bên phải, công cụ tìm kiếm và tuỳ chọn ở bên trái"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# tiện ích}other{# tiện ích}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# lối tắt}other{# lối tắt}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Công việc"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Cuộc trò chuyện"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Ghi chú"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Thông tin hữu ích ngay trong tầm tay bạn"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Để nhận thông tin mà không cần mở các ứng dụng, bạn có thể thêm tiện ích vào màn hình chính"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Thêm"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Thêm tiện ích <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Nhấn để thay đổi chế độ cài đặt tiện ích"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Tôi hiểu"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Thay đổi chế độ cài đặt tiện ích"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Tìm kiếm ứng dụng"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Đang tải ứng dụng…"</string>
@@ -84,8 +86,9 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Gỡ cài đặt"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Thông tin ứng dụng"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Cài đặt ở chế độ riêng tư"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Gỡ cài đặt ứng dụng"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Cài đặt"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Không đề xuất ứng dụng"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Không gợi ý ứng dụng"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Ghim ứng dụng dự đoán"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"cài đặt lối tắt"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Cho phép ứng dụng thêm lối tắt mà không cần sự can thiệp của người dùng."</string>
@@ -124,7 +127,7 @@
<string name="msg_missing_notification_access" msgid="281113995110910548">"Để hiển thị Dấu chấm thông báo, hãy bật thông báo ứng dụng cho <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="title_change_settings" msgid="1376365968844349552">"Thay đổi cài đặt"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Hiện dấu chấm thông báo"</string>
- <string name="developer_options_title" msgid="700788437593726194">"Tùy chọn cho nhà phát triển"</string>
+ <string name="developer_options_title" msgid="700788437593726194">"Tuỳ chọn cho nhà phát triển"</string>
<string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Thêm biểu tượng ứng dụng vào màn hình chính"</string>
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Cho ứng dụng mới"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"Không xác định"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"Đang cài đặt <xliff:g id="NAME">%1$s</xliff:g>, hoàn tất <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Đang tải xuống <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> hoàn tất"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Đang chờ cài đặt <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"Đã lưu trữ <xliff:g id="NAME">%1$s</xliff:g> Nhấn để tải xuống."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> đã được lưu trữ. Hãy nhấn để tải xuống và khôi phục."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Cần cập nhật ứng dụng"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"Ứng dụng cho biểu tượng này chưa được cập nhật. Bạn có thể cập nhật theo cách thủ công để bật lại phím tắt này hoặc xóa biểu tượng."</string>
<string name="dialog_update" msgid="2178028071796141234">"Cập nhật"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Bộ lọc"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Không thực hiện được thao tác: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Không gian riêng tư"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Luôn khoá và ẩn các ứng dụng riêng tư"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Nhấn để thiết lập hoặc mở"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Riêng tư"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Cài đặt không gian riêng tư"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Khoá/mở khoá không gian riêng tư"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Riêng tư, đã mở khoá."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Riêng tư, đã khoá."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Khoá"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Chuyển đổi sang không gian riêng tư"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Cài đặt ứng dụng"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Cài đặt"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Cài đặt ứng dụng vào Không gian riêng tư"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Bong bóng bổ sung"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 4c95167..7a76158 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分屏"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的应用信息"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s的使用设置"</string>
<string name="save_app_pair" msgid="5647523853662686243">"保存应用组合"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"在该设备上无法使用此应用对"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"已将“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件添加到主屏幕"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"建议"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"您的日常必需品"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"更多相关新闻"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"您的休闲区"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"达成您的健身目标"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"天气早知道"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"您可能还会喜欢"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"必备之选"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"新闻与杂志"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"娱乐"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"社交"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"为您推荐"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"右边是<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>微件,左边是搜索功能和选项"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# 个微件}other{# 个微件}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# 个快捷方式}other{# 个快捷方式}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"工作"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"对话"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"记事"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"实用信息触手可及"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"要想不打开应用就能获取信息,您可以将相应微件添加到主屏幕"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"添加"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"添加“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"点按即可更改微件设置"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"知道了"</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>
@@ -84,8 +86,9 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"卸载"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"应用信息"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"安装到私密个人资料中"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"卸载应用"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"安装"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"不要提供应用建议"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"不要推荐此应用"</string>
<string name="pin_prediction" msgid="4196423321649756498">"固定预测的应用"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"安装快捷方式"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"允许应用自行添加快捷方式。"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"正在安装<xliff:g id="NAME">%1$s</xliff:g>,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"正在下载<xliff:g id="NAME">%1$s</xliff:g>,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>正在等待安装"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"已归档“<xliff:g id="NAME">%1$s</xliff:g>”。点按即可下载。"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"已归档“<xliff:g id="NAME">%1$s</xliff:g>”。点按即可进行下载并恢复。"</string>
<string name="dialog_update_title" msgid="114234265740994042">"需要更新应用"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"此图标对应的应用未更新。您可以手动更新以重新启用该快捷方式,或者移除此图标。"</string>
<string name="dialog_update" msgid="2178028071796141234">"更新"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"过滤器"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"失败:<xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"私密空间"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"让专用应用保持锁定状态并隐藏"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"点按即可设置或打开"</string>
<string name="ps_container_title" msgid="4391796149519594205">"私密"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"私密空间设置"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"锁定/解锁私密空间"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"私密,未锁定。"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"私密,已锁定。"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"锁定"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"私密空间转换"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"安装应用"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"安装"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"将应用安装到私密空间"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"菜单"</string>
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index ff71ad2..ffd9e0b 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割螢幕"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的應用程式資料"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"「%1$s」的用量設定"</string>
<string name="save_app_pair" msgid="5647523853662686243">"儲存應用程式配對"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"此裝置不支援此應用程式配對"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"已經將「<xliff:g id="WIDGET_NAME">%1$s</xliff:g>」小工具加咗去主畫面"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"建議"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"你的日常必需品"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"你的專屬新聞"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"放鬆專區"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"向健身目標邁進"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"隨時掌握天氣資料"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"相關推薦"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"必備之選"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"新聞和雜誌"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"娛樂"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"社交"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"為你推薦"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"右邊係「<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>」小工具,左邊係搜尋功能同選項"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# 個小工具}other{# 個小工具}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# 個捷徑}other{# 個捷徑}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"工作"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"對話"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"做筆記"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"實用資訊,唾手可得"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"只要將小工具新增至主畫面,就可以直接查看資料,無需開啟應用程式"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"新增"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"加<xliff:g id="WIDGET_NAME">%1$s</xliff:g>小工具"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"輕按即可變更小工具設定"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"知道了"</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>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"解除安裝"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"應用程式資料"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"安裝在私人資料夾中"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"解除安裝應用程式"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"安裝"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"不要提供應用程式建議"</string>
<string name="pin_prediction" msgid="4196423321649756498">"固定預測"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"正在安裝「<xliff:g id="NAME">%1$s</xliff:g>」(已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"正在下載 <xliff:g id="NAME">%1$s</xliff:g>,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"正在等待安裝 <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"「<xliff:g id="NAME">%1$s</xliff:g>」已封存。輕按即可下載。"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"「<xliff:g id="NAME">%1$s</xliff:g>」已封存。輕按即可下載並還原。"</string>
<string name="dialog_update_title" msgid="114234265740994042">"必須更新應用程式"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"你尚未更新這個圖示代表的應用程式。你可以手動更新以重新啟用此快速鍵,或者移除圖示。"</string>
<string name="dialog_update" msgid="2178028071796141234">"更新"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"篩選器"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"操作失敗:<xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"私人空間"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"讓私人應用程式保持鎖定及隱藏"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"輕按即可設定或開啟"</string>
<string name="ps_container_title" msgid="4391796149519594205">"私人"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"「私人空間」設定"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"鎖定/解鎖「私人空間」"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"私人,未鎖定。"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"私人,已鎖定。"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"上鎖"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"轉為「私人空間」"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"安裝應用程式"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"安裝"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"將應用程式安裝在「私人空間」中"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"顯示更多"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 451efc7..264d607 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -27,27 +27,30 @@
<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>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割畫面"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"「%1$s」的應用程式資訊"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"「%1$s」的用量設定"</string>
<string name="save_app_pair" msgid="5647523853662686243">"儲存應用程式配對"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"這部裝置不支援這組應用程式配對"</string>
<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="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_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>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"已將「<xliff:g id="WIDGET_NAME">%1$s</xliff:g>」小工具新增到主畫面"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"建議"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"生活好幫手"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"你的專屬新聞"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"放鬆專區"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"達成健身目標"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"隨時掌握天氣資訊"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"你可能也會喜歡的內容"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"常用項目"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"新聞與雜誌"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"娛樂"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"社群"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"個人化建議"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"右邊是「<xliff:g id="SELECTED_HEADER">%1$s</xliff:g>」小工具,左邊是搜尋功能和選項"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# 項小工具}other{# 項小工具}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# 個捷徑}other{# 個捷徑}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"工作"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"對話"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"做筆記"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"實用資訊隨手可得"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"只要將小工具新增到主畫面,就可以直接查看資訊,不必開啟應用程式"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"新增"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"新增「<xliff:g id="WIDGET_NAME">%1$s</xliff:g>」小工具"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"輕觸即可變更小工具設定"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"我知道了"</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>
@@ -83,9 +85,10 @@
<string name="remove_drop_target_label" msgid="7812859488053230776">"移除"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"解除安裝"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"應用程式資訊"</string>
- <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"安裝在私人資料夾中"</string>
+ <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"安裝在私人空間中"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"解除安裝應用程式"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"安裝"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"不要提供應用程式建議"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"不要建議此應用程式"</string>
<string name="pin_prediction" msgid="4196423321649756498">"固定預測的應用程式"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"安裝捷徑"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"允許應用程式自動新增捷徑。"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"正在安裝「<xliff:g id="NAME">%1$s</xliff:g>」(已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"正在下載「<xliff:g id="NAME">%1$s</xliff:g>」,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"正在等待安裝「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"已封存「<xliff:g id="NAME">%1$s</xliff:g>」。輕觸即可下載。"</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"「<xliff:g id="NAME">%1$s</xliff:g>」已封存。輕觸即可下載並還原。"</string>
<string name="dialog_update_title" msgid="114234265740994042">"必須更新應用程式"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"這個圖示代表的應用程式未更新。手動更新即可重新啟用這個捷徑,你也可以移除圖示。"</string>
<string name="dialog_update" msgid="2178028071796141234">"更新"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"篩選器"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"失敗:<xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"私人空間"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"讓私人應用程式保持鎖定及隱藏的狀態"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"輕觸即可設定或開啟"</string>
<string name="ps_container_title" msgid="4391796149519594205">"私人"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"私人空間設定"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"鎖定/取消鎖定私人空間"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"私人,未鎖定。"</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"私人,已鎖定。"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"鎖定"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"轉換私人空間狀態"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"安裝應用程式"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"安裝"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"將應用程式安裝在私人空間中"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"溢位"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 8388bb7..be6cd49 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -27,27 +27,30 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Amawijethi akhutshaziwe kwimodi yokuphepha"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Isinqamuleli asitholakali"</string>
<string name="home_screen" msgid="5629429142036709174">"Ikhaya"</string>
+ <string name="set_default_home_app" msgid="5808906607627586381">"Setha i-<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> njenge-app yasekhaya ezenzakalelayo Kumasethingi"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Hlukanisa isikrini"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Ulwazi lwe-App ye-%1$s"</string>
+ <string name="split_app_usage_settings" msgid="7214375263347964093">"Amasethingi okusetshenziswa ka-%1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Londoloza i-app ebhangqiwe"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Lokhu kubhanqwa kwe-app akusekelwa kule divayisi"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Vula idivayisi ukuze usebenzise lokhu kubhanqwa kwe-app"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"Ukubhangqwa kwe-app akutholakali"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Thinta uphinde ubambe ukuze uhambise iwijethi."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Thepha kabili uphinde ubambe ukuze uhambise iwijethi noma usebenzise izindlela ezingokwezifiso."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ububanzi ngokungu-%2$d ukuya phezulu"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Iwijethi elingu-<xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Iwijethi ye-<xliff:g id="WIDGET_NAME">%1$s</xliff:g>, ububanzi obungu-%2$d ngokuphakama okungu-%3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Thinta uphinde ubambe iwijethi ukuyihambisa kusikrini sasekhaya"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Faka kusikrini sasekhaya"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Iwijethi ye-<xliff:g id="WIDGET_NAME">%1$s</xliff:g> yengezwe kusikrini sasekhaya"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Iziphakamiso"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="1722113555721820766">"Okusemqoka kwakho kwansuku zonke"</string>
- <string name="news_widget_recommendation_category_label" msgid="3908242346768119070">"Izindaba Zakho"</string>
- <string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Indawo Ozipholela Kuyo"</string>
- <string name="fitness_widget_recommendation_category_label" msgid="2657652999128882431">"Finyelela Imigomo Yakho Yokufaneleka"</string>
- <string name="weather_widget_recommendation_category_label" msgid="6712678763480668598">"Hlale Wazi Ngesimo Sezulu"</string>
- <string name="others_widget_recommendation_category_label" msgid="897876078077284733">"Ungase Futhi Uthande"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Okusemqoka"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Izindaba nomagazini"</string>
+ <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Okokozijabulisa"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Okomphakathi"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Okuphakanyiselwe wena"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"Amawijethi okuthi <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> kwesokudla, ukusesha nokukhethwayo kwesobunxele"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{iwijethi #}one{amawijethi #}other{amawijethi #}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{isinqamuleli #}one{izinqamuleli #}other{izinqamuleli #}}"</string>
@@ -61,10 +64,9 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Umsebenzi"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Izingxoxo"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Ukuthatha amanothi"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Ulwazi oluwusizo phambi nje kwakho"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Ukuze uthole ulwazi ngaphandle kokuvula ama-app, ungakwazi ukwengeza amawijethi kusikrini sakho sasekhaya"</string>
+ <string name="widget_add_button_label" msgid="2761267068711937179">"Engeza"</string>
+ <string name="widget_add_button_content_description" msgid="1810530016360039643">"Engeza iwijethi ye-<xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Thepha ukuze ushintshe amasethingi ewijethi"</string>
- <string name="widget_education_close_button" msgid="8676165703104836580">"Ngiyezwa"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Shintsha amasethingi ewijethi"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Sesha izinhlelo zokusebenza"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Ilayisha izinhlelo zokusebenza..."</string>
@@ -84,6 +86,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Khipha"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Ulwazi nge-app"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Faka ngokugodliwe"</string>
+ <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Khipha i-app"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Faka"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Ungaphakamisi uhlelo lokusebenza"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Ukubikezela Iphinikhodi"</string>
@@ -122,7 +125,7 @@
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Valiwe"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"Ukufinyelela izaziso kuyadingeka"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Ukuze ubonisa amcashazi esaziso, vula izaziso zohlelo lokusebenza ze-<xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="title_change_settings" msgid="1376365968844349552">"Shintsha izilungiselelo"</string>
+ <string name="title_change_settings" msgid="1376365968844349552">"Shintsha amasethingi"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Bonisa amacashazi esaziso"</string>
<string name="developer_options_title" msgid="700788437593726194">"Izinketho zonjiniyela"</string>
<string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Engeza izithonjana ze-app kusikrini sasekhaya"</string>
@@ -135,7 +138,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"I-<xliff:g id="NAME">%1$s</xliff:g> iyafakwa, seyiqede <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"I-<xliff:g id="NAME">%1$s</xliff:g> iyalandwa, <xliff:g id="PROGRESS">%2$s</xliff:g> kuqediwe"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ilinde ukufakwa"</string>
- <string name="app_archived_title" msgid="9124290918876665128">"I-<xliff:g id="NAME">%1$s</xliff:g> ifakwe kungobo yomlando. Thepha ukuze udawunilode."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"Okuthi <xliff:g id="NAME">%1$s</xliff:g> kufakwe kungobo yomlando. Thepha ukuze udawunilode futhi ubuyisele."</string>
<string name="dialog_update_title" msgid="114234265740994042">"Kudingeka isibuyekezo se-app"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"I-app yalesi sithonjana ibuyekeziwe. Ungabuyekeza mathupha ukuze uphinde unike amandla lesi sinqamuleli, noma ususe isithonjana."</string>
<string name="dialog_update" msgid="2178028071796141234">"Vuselela"</string>
@@ -183,13 +186,13 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Hlunga"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Yehlulekile: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Isikhala esiyimfihlo"</string>
- <string name="private_space_secondary_label" msgid="611902414159280263">"Gcina ama-app angasese ekhiyiwe futhi efihliwe"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Thepha ukuze usethe noma uvule"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Okuyimfihlo"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Amasethingi Esikhala Esiyimfihlo"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Khiya/Vula Isikhala Esiyimfihlo"</string>
+ <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Iyimfihlo, ivuliwe."</string>
+ <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Iyimfihlo, ikhiyiwe."</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"Khiya"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Ukuguqulwa Kwendawo Yangasese"</string>
- <string name="ps_add_button_label" msgid="8611055839242385935">"Faka ama-app"</string>
+ <string name="ps_add_button_label" msgid="8127988716897128773">"Faka"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"Faka ama-app Endaweni Engasese"</string>
- <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Ukugcwala kakhulu"</string>
</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 4a0b5e8..be8b2e1 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -43,14 +43,16 @@
<attr name="popupNotificationDotColor" format="color" />
<attr name="notificationDotColor" format="color" />
<attr name="focusOutlineColor" format="color" />
+ <attr name="focusInnerOutlineColor" format="color" />
- <attr name="folderPaginationColor" format="color" />
+ <attr name="pageIndicatorDotColor" format="color" />
<attr name="folderPreviewColor" format="color" />
<attr name="folderBackgroundColor" format="color" />
<attr name="folderIconRadius" format="float" />
<attr name="folderIconBorderColor" format="color" />
<attr name="folderTextColor" format="color" />
<attr name="folderHintTextColor" format="color" />
+ <attr name="appPairSurfaceInFolder" format="color" />
<attr name="isFolderDarkText" format="boolean" />
<attr name="workspaceAccentColor" format="color" />
<attr name="workspaceSurfaceColor" format="color" />
@@ -72,6 +74,10 @@
<attr name="widgetPickerSelectedTabTextColor" format="color"/>
<attr name="widgetPickerUnselectedTabTextColor" format="color"/>
<attr name="widgetPickerCollapseHandleColor" format="color"/>
+ <attr name="widgetPickerAddButtonBackgroundColor" format="color"/>
+ <attr name="widgetPickerAddButtonTextColor" format="color"/>
+ <attr name="widgetCellTitleColor" format="color" />
+ <attr name="widgetCellSubtitleColor" format="color" />
<!-- BubbleTextView specific attributes. -->
<declare-styleable name="BubbleTextView">
@@ -200,17 +206,6 @@
defaults to 2 * numHotseatIcons -->
<attr name="numExtendedHotseatIcons" format="integer" />
- <!-- alignment of hotseat to the grid.
- Not applicable for 3 button mode when taskbar is enabled -->
- <!-- defaults to numColumns, if not specified -->
- <attr name="hotseatColumnSpan" format="integer" />
- <!-- defaults to numColumns, if not specified -->
- <attr name="hotseatColumnSpanLandscape" format="integer" />
- <!-- defaults to numColumns, if not specified -->
- <attr name="hotseatColumnSpanTwoPanelLandscape" format="integer" />
- <!-- defaults to numColumns, if not specified -->
- <attr name="hotseatColumnSpanTwoPanelPortrait" format="integer" />
-
<!-- Spacing to have at the end of the nav buttons in large screen 3 button nav,
defaults to @dimen/taskbar_button_margin_default -->
<attr name="inlineNavButtonsEndSpacing" format="reference" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 6a484d7..ce80964 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -73,8 +73,8 @@
<color name="folder_preview_light">#7FCFFF</color>
<color name="folder_preview_dark">#1E1F20</color>
- <color name="folder_pagination_color_light">#0B57D0</color>
- <color name="folder_pagination_color_dark">#A8C7FA</color>
+ <color name="pagination_indicator_dot_color_light">#0B57D0</color>
+ <color name="pagination_indicator_dot_color_dark">#A8C7FA</color>
<color name="text_color_primary_dark">#FFFFFFFF</color>
<color name="text_color_secondary_dark">#FFFFFFFF</color>
@@ -90,6 +90,8 @@
<color name="drop_target_hover_button_color_light">#D3E3FD</color>
<color name="drop_target_hover_button_color_dark">#0842A0</color>
+ <color name="taskbar_running_app_indicator_color">#000000</color>
+
<color name="preload_icon_accent_color_light">#00668B</color>
<color name="preload_icon_background_color_light">#B5CAD7</color>
<color name="preload_icon_accent_color_dark">#4BB6E8</color>
@@ -113,6 +115,10 @@
<color name="widget_picker_selected_tab_text_color_light">#FFFFFF</color>
<color name="widget_picker_unselected_tab_text_color_light">#444746</color>
<color name="widget_picker_collapse_handle_color_light">#C4C7C5</color>
+ <color name="widget_picker_add_button_background_color_light">#0B57D0</color>
+ <color name="widget_picker_add_button_text_color_light">#0B57D0</color>
+ <color name="widget_cell_title_color_light">@color/material_color_on_surface</color>
+ <color name="widget_cell_subtitle_color_light">@color/material_color_on_surface_variant</color>
<color name="widget_picker_primary_surface_color_dark">#1F2020</color>
<color name="widget_picker_secondary_surface_color_dark">#393939</color>
@@ -128,6 +134,10 @@
<color name="widget_picker_selected_tab_text_color_dark">#2D312F</color>
<color name="widget_picker_unselected_tab_text_color_dark">#C4C7C5</color>
<color name="widget_picker_collapse_handle_color_dark">#444746</color>
+ <color name="widget_picker_add_button_background_color_dark">#062E6F</color>
+ <color name="widget_picker_add_button_text_color_dark">#FFFFFF</color>
+ <color name="widget_cell_title_color_dark">@color/material_color_on_surface</color>
+ <color name="widget_cell_subtitle_color_dark">@color/material_color_on_surface_variant</color>
<color name="material_color_on_secondary_fixed_variant">#3F4759</color>
<color name="material_color_on_tertiary_fixed_variant">#583E5B</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index 21d6024..2a3b588 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -17,38 +17,23 @@
<!-- Miscellaneous -->
<bool name="config_largeHeap">false</bool>
- <integer name="extracted_color_gradient_alpha">153</integer>
-
<!-- A string pointer to the original app name string. This allows derived projects to
easily override the app name without providing all translations -->
<string name="derived_app_name" translatable="false">@string/app_name</string>
- <!-- String representing the intent for search on the apps market. To specify a query, add
- q=<query> to the data to the intent -->
- <string name="market_search_intent" translatable="false">market://search?c=apps</string>
-
<!-- String representing the intent to delete a package.-->
<string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;end</string>
<!-- String representing the fragment class for settings activity.-->
<string name="settings_fragment_name" translatable="false">com.android.launcher3.settings.SettingsActivity$LauncherSettingsFragment</string>
- <!-- DragController -->
- <item type="id" name="drag_event_parity" />
-
<!-- AllApps & Launcher transitions -->
- <!-- The duration of the animation from search hint to text entry -->
- <integer name="config_searchHintAnimationDuration">50</integer>
-
<!-- The duration of the PagedView page snap animation -->
<integer name="config_pageSnapAnimationDuration">750</integer>
<!-- The duration of the PagedView page snap animation -->
<integer name="config_keyboardTaskFocusSnapAnimationDuration">750</integer>
- <!-- View tag key used to store SpringAnimation data. -->
- <item type="id" name="spring_animation_tag" />
-
<!-- View tag key used to determine if we should fade in the child views.. -->
<string name="popup_container_iterate_children" translatable="false">popup_container_iterate_children</string>
@@ -91,74 +76,35 @@
<string name="taskbar_model_callbacks_factory_class" translatable="false"></string>
<string name="taskbar_view_callbacks_factory_class" translatable="false"></string>
<string name="launcher_restore_event_logger_class" translatable="false"></string>
-
- <!-- View ID to use for QSB widget -->
- <item type="id" name="qsb_widget" />
-
- <!-- View ID used by cell layout to jail its content -->
- <item type="id" name="cell_layout_jail_id" />
-
- <!-- View IDs to store item highlight information -->
- <item type="id" name="view_unhighlight_background" />
-
- <!-- view ID used to restore work tab state -->
- <item type="id" name="work_tab_state_id" />
-
- <!-- Menu id for feature flags -->
- <item type="id" name="menu_apply_flags" />
+ <string name="taskbar_edu_tooltip_controller_class" translatable="false"></string>
+ <!-- Used for determining category of a widget presented in widget recommendations. -->
+ <string name="widget_recommendation_category_provider_class" translatable="false"></string>
+ <string name="api_wrapper_class" translatable="false"></string>
<!-- Default packages -->
<string name="wallpaper_picker_package" translatable="false"></string>
<string name="local_colors_extraction_class" translatable="false"></string>
<string name="search_session_manager_class" translatable="false"></string>
-
- <!-- Accessibility actions -->
- <item type="id" name="action_remove" />
- <item type="id" name="action_uninstall" />
- <item type="id" name="action_reconfigure" />
- <item type="id" name="action_add_to_workspace" />
- <item type="id" name="action_move" />
- <item type="id" name="action_move_to_workspace" />
- <item type="id" name="action_move_screen_backwards" />
- <item type="id" name="action_move_screen_forwards" />
- <item type="id" name="action_resize" />
- <item type="id" name="action_deep_shortcuts" />
- <item type="id" name="action_remote_action_shortcut" />
- <item type="id" name="action_dismiss_prediction" />
- <item type="id" name="action_pin_prediction"/>
-
- <!-- QSB IDs. DO not change -->
- <item type="id" name="search_container_workspace" />
- <item type="id" name="search_container_all_apps" />
- <item type="id" name="search_container_hotseat" />
+ <string name="plugin_manager_wrapper_class" translatable="false"></string>
<!-- Scalable Grid configuration -->
<!-- This is a float because it is converted to dp later in DeviceProfile -->
<dimen name="hotseat_bar_bottom_space_default">48</dimen>
<dimen name="hotseat_qsb_space_default">0</dimen>
- <!-- Recents -->
- <item type="id" name="overview_panel"/>
-
<!-- Whether to enable background preloading of task thumbnails. -->
<bool name="config_enableTaskSnapshotPreloading">true</bool>
<!-- Configuration resources -->
- <item name="all_apps_spring_damping_ratio" type="dimen" format="float">0.75</item>
- <item name="all_apps_spring_stiffness" type="dimen" format="float">600</item>
-
<item name="dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.73</item>
<item name="dismiss_task_trans_y_stiffness" type="dimen" format="float">800</item>
- <item name="dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.73</item>
- <item name="dismiss_task_trans_x_stiffness" type="dimen" format="float">800</item>
-
- <item name="horizontal_spring_damping_ratio" type="dimen" format="float">0.8</item>
- <item name="horizontal_spring_stiffness" type="dimen" format="float">250</item>
-
<item name="swipe_up_rect_scale_damping_ratio" type="dimen" format="float">0.75</item>
<item name="swipe_up_rect_scale_stiffness" type="dimen" format="float">200</item>
<item name="swipe_up_rect_scale_higher_stiffness" type="dimen" format="float">400</item>
+ <!-- Flag: enableScalingRevealHomeAnimation() -->
+ <item name="swipe_up_rect_scale_damping_ratio_v2" type="dimen" format="float">0.99</item>
+ <item name="swipe_up_rect_scale_stiffness_v2" type="dimen" format="float">500</item>
<item name="swipe_up_rect_xy_fling_friction" type="dimen" format="float">1.5</item>
@@ -166,6 +112,11 @@
<item name="swipe_up_rect_xy_damping_ratio" type="dimen" format="float">0.8</item>
<item name="swipe_up_rect_xy_stiffness" type="dimen" format="float">200</item>
+ <!-- Flag: enableScalingRevealHomeAnimation() -->
+ <item name="swipe_up_rect_x_damping_ratio" type="dimen" format="float">0.965</item>
+ <item name="swipe_up_rect_x_stiffness" type="dimen" format="float">450</item>
+ <item name="swipe_up_rect_y_damping_ratio" type="dimen" format="float">0.95</item>
+ <item name="swipe_up_rect_y_stiffness" type="dimen" format="float">400</item>
<!-- Taskbar -->
<!-- This is a float because it is converted to dp later in DeviceProfile -->
@@ -190,6 +141,12 @@
<dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
<dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
<dimen name="swipe_up_max_velocity">7.619dp</dimen>
+ <!-- Flag: enableScalingRevealHomeAnimation() -->
+ <item name="swipe_up_min_velocity_x_px_per_s" type="dimen" format="integer">300</item>
+ <item name="swipe_up_max_velocity_x_px_per_s" type="dimen" format="integer">500</item>
+ <item name="swipe_up_min_velocity_y_px_per_s" type="dimen" format="integer">2000</item>
+ <item name="swipe_up_max_velocity_y_px_per_s" type="dimen" format="integer">5000</item>
+ <item name="swipe_up_max_velocity_fall_off_factor" type="dimen" format="float">1.4</item>
<array name="dynamic_resources">
<item>@dimen/swipe_up_scale_start</item>
@@ -198,14 +155,6 @@
<string-array name="filtered_components" ></string-array>
- <!-- Widget component names to be included in weather category of widget suggestions. -->
- <string-array name="weather_recommendations"></string-array>
- <!-- Widget component names to be included in fitness category of widget suggestions. -->
- <string-array name="fitness_recommendations"></string-array>
-
- <!-- Name of the class used to generate colors from the wallpaper colors. Must be implementing the LauncherAppWidgetHostView.ColorGenerator interface. -->
- <string name="color_generator_class" translatable="false"/>
-
<!-- Swipe back to home related -->
<dimen name="swipe_back_window_scale_x_margin">10dp</dimen>
<dimen name="swipe_back_window_corner_radius">40dp</dimen>
@@ -261,9 +210,6 @@
<!-- Used for custom widgets -->
<array name="custom_widget_providers"/>
- <!-- Used for determining category of a widget presented in widget recommendations. -->
- <string name="widget_recommendation_category_provider_class" translatable="false"></string>
-
<!-- Embed parameters -->
<dimen name="activity_split_ratio" format="float">0.5</dimen>
<integer name="min_width_split">720</integer>
@@ -272,4 +218,11 @@
<string-array name="skip_private_profile_shortcut_packages" translatable="false">
<item>com.android.settings</item>
</string-array>
+
+ <!-- Legacy list of components supporting multiple instances.
+ DO NOT ADD TO THIS LIST. Apps should use the PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
+ property to declare multi-instance support in V+. This resource should match the resource
+ of the same name in SystemUI. -->
+ <string-array name="config_appsSupportMultiInstancesSplit">
+ </string-array>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a912e2d..05724e2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -22,7 +22,7 @@
<dimen name="dynamic_grid_edge_margin">10.77dp</dimen>
<dimen name="dynamic_grid_left_right_margin">8dp</dimen>
<!-- Minimum amount of next page visible in spring loaded mode -->
- <dimen name="dynamic_grid_spring_loaded_min_next_space_visible">24dp</dimen>
+ <dimen name="dynamic_grid_spring_loaded_min_next_space_visible">48dp</dimen>
<dimen name="dynamic_grid_cell_border_spacing">16dp</dimen>
<dimen name="cell_layout_padding">10.77dp</dimen>
@@ -147,13 +147,15 @@
<dimen name="work_fab_height">56dp</dimen>
<dimen name="work_fab_radius">16dp</dimen>
<dimen name="work_fab_icon_size">24dp</dimen>
- <dimen name="work_fab_text_start_margin">8dp</dimen>
+ <dimen name="work_fab_icon_end_margin">12dp</dimen>
+ <dimen name="work_fab_text_end_margin">16dp</dimen>
<dimen name="work_card_padding_horizontal">10dp</dimen>
<dimen name="work_fab_width">214dp</dimen>
<dimen name="work_card_button_height">52dp</dimen>
<dimen name="work_fab_margin">16dp</dimen>
<dimen name="work_fab_margin_bottom">20dp</dimen>
- <dimen name="work_mode_fab_padding">16dp</dimen>
+ <dimen name="work_mode_fab_background_start_padding">16dp</dimen>
+ <dimen name="work_mode_fab_background_end_padding">4dp</dimen>
<dimen name="work_profile_footer_padding">20dp</dimen>
<dimen name="work_edu_card_margin">16dp</dimen>
<dimen name="work_edu_card_radius">16dp</dimen>
@@ -165,7 +167,6 @@
<!-- (x) icon button inside work edu card -->
<dimen name="rounded_button_width">24dp</dimen>
<dimen name="x_icon_size">16dp</dimen>
- <dimen name="x_icon_padding">4dp</dimen>
<!-- rounded button shown inside card views, and snack bars -->
<dimen name="padded_rounded_button_height">48dp</dimen>
@@ -176,18 +177,41 @@
<!-- Widget tray -->
<dimen name="widget_cell_vertical_padding">8dp</dimen>
- <dimen name="widget_cell_horizontal_padding">16dp</dimen>
- <dimen name="widget_cell_font_size">14sp</dimen>
+ <dimen name="widget_cell_horizontal_padding">8dp</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>
+ <dimen name="widget_cell_dims_font_size">14sp</dimen>
+ <integer name="widget_cell_dims_font_weight">400</integer>
+ <dimen name="widget_cell_dims_line_height">20sp</dimen>
+ <dimen name="widget_cell_description_font_size">12sp</dimen>
+ <integer name="widget_cell_description_font_weight">400</integer>
+ <dimen name="widget_cell_description_line_height">16sp</dimen>
<dimen name="widget_cell_app_icon_size">24dp</dimen>
<dimen name="widget_cell_app_icon_padding">8dp</dimen>
+ <dimen name="widget_cell_add_button_height">48dp</dimen>
+ <dimen name="widget_cell_add_button_start_padding">8dp</dimen>
+ <dimen name="widget_cell_add_icon_button_start_padding">16dp</dimen>
+ <dimen name="widget_cell_add_button_end_padding">16dp</dimen>
+ <dimen name="widget_cell_add_button_scroll_padding">24dp</dimen>
+ <dimen name="widget_cell_add_button_font_size">14sp</dimen>
+ <integer name="widget_cell_add_button_font_weight">500</integer>
+ <dimen name="widget_cell_add_button_line_height">20sp</dimen>
+ <dimen name="widget_cell_add_button_drawable_padding">8dp</dimen>
+ <dimen name="widget_cell_add_button_drawable_width">19dp</dimen>
<dimen name="widget_tabs_button_horizontal_padding">4dp</dimen>
<dimen name="widget_tabs_horizontal_padding">16dp</dimen>
<dimen name="widget_apps_tabs_vertical_padding">6dp</dimen>
<dimen name="widget_picker_landscape_tablet_left_right_margin">117dp</dimen>
<dimen name="widget_picker_two_panels_left_right_margin">0dp</dimen>
+ <dimen name="widget_picker_header_app_title_font_size">16sp</dimen>
+ <integer name="widget_picker_header_app_title_font_weight">500</integer>
+ <dimen name="widget_picker_header_app_title_line_height">24sp</dimen>
+ <dimen name="widget_picker_header_app_subtitle_font_size">14sp</dimen>
+ <integer name="widget_picker_header_app_subtitle_font_weight">400</integer>
+ <dimen name="widget_picker_header_app_subtitle_line_height">20sp</dimen>
<dimen name="widget_recommendations_table_vertical_padding">8dp</dimen>
- <dimen name="widget_recommendations_table_horizontal_padding">16dp</dimen>
<!-- Bottom margin for the search and recommended widgets container without work profile -->
<dimen name="search_and_recommended_widgets_container_bottom_margin">16dp</dimen>
<!-- Bottom margin for the search and recommended widgets container with work profile -->
@@ -198,10 +222,14 @@
<dimen name="widget_list_header_view_vertical_padding">20dp</dimen>
<dimen name="widget_list_entry_spacing">2dp</dimen>
- <dimen name="widget_list_horizontal_margin">16dp</dimen>
+ <!-- Less margin on sides to let widgets table width be close to the workspace width. -->
+ <dimen name="widget_list_horizontal_margin">11dp</dimen>
<!-- Margin applied to the recycler view with search bar & the list of widget apps below it. -->
<dimen name="widget_list_left_pane_horizontal_margin">0dp</dimen>
<dimen name="widget_list_horizontal_margin_two_pane">24dp</dimen>
+ <dimen name="widget_picker_vertical_margin_right_pane">24dp</dimen>
+ <!-- Margin on sides of the widgets scrollview in widgets bottom sheet -->
+ <dimen name="widget_bottom_sheet_horizontal_margin">16dp</dimen>
<dimen name="widget_preview_shadow_blur">0.5dp</dimen>
<dimen name="widget_preview_key_shadow_distance">1dp</dimen>
@@ -273,7 +301,6 @@
<!-- Sizes for managed profile badges -->
<dimen name="profile_badge_size">24dp</dimen>
- <dimen name="profile_badge_margin">5dp</dimen>
<dimen name="profile_badge_minimum_top">2dp</dimen>
<!-- Shadows and outlines -->
@@ -293,6 +320,8 @@
<dimen name="bg_popup_item_height">52dp</dimen>
<dimen name="bg_popup_item_vertical_padding">12dp</dimen>
<dimen name="pre_drag_view_scale">6dp</dimen>
+ <!-- Minimum size of the widget dragged view to keep it visible under the finger. -->
+ <dimen name="widget_drag_view_min_scale_down_size">70dp</dimen>
<!-- an icon with shortcuts must be dragged this far before the container is removed. -->
<dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
<!-- Possibly related to b/235886078, icon needs to be scaled up to match expected visual size of 32 dp -->
@@ -386,6 +415,12 @@
<dimen name="min_hotseat_icon_space">18dp</dimen>
<dimen name="max_hotseat_icon_space">50dp</dimen>
<dimen name="min_hotseat_qsb_width">0dp</dimen>
+ <dimen name="taskbar_running_app_indicator_height">0dp</dimen>
+ <dimen name="taskbar_running_app_indicator_width">0dp</dimen>
+ <dimen name="taskbar_running_app_indicator_top_margin">0dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_height">0dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_width">0dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_top_margin">0dp</dimen>
<!-- Transient taskbar (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="transient_taskbar_padding">0dp</dimen>
@@ -396,7 +431,7 @@
<dimen name="transient_taskbar_clamped_offset_bound">0dp</dimen>
<dimen name="taskbar_icon_spacing">0dp</dimen>
<dimen name="taskbar_nav_buttons_size">0dp</dimen>
- <dimen name="taskbar_contextual_button_margin">0dp</dimen>
+ <dimen name="taskbar_contextual_button_suw_margin">0dp</dimen>
<dimen name="taskbar_hotseat_nav_spacing">0dp</dimen>
<dimen name="taskbar_button_margin_default">0dp</dimen>
<dimen name="taskbar_button_space_inbetween">0dp</dimen>
@@ -413,6 +448,9 @@
<!-- Size of the maximum radius for the enforced rounded rectangles. -->
<dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
+ <!-- Size of the radius for the rounded corners of Persistent Taskbar. -->
+ <dimen name="persistent_taskbar_corner_radius">16dp</dimen>
+
<!-- Base Swipe Detector, speed in dp/s -->
<dimen name="base_swift_detector_fling_release_velocity">1dp</dimen>
@@ -445,9 +483,15 @@
<dimen name="split_instructions_bottom_margin_phone_landscape">24dp</dimen>
<dimen name="split_instructions_bottom_margin_phone_portrait">60dp</dimen>
<dimen name="split_instructions_start_margin_cancel">8dp</dimen>
+ <dimen name="split_divider_handle_region_width">96dp</dimen>
+ <dimen name="split_divider_handle_region_height">48dp</dimen>
<dimen name="focus_outline_radius">16dp</dimen>
- <dimen name="focus_outline_stroke_width">3dp</dimen>
+ <dimen name="focus_inner_outline_radius">14dp</dimen>
+ <dimen name="focus_outline_stroke_width">2dp</dimen>
+ <!-- -4dp for double stroke focus outlines, -2dp for padding between outline and widget,
+ make it negative to outset the rect and leave space for drawing focus outline and padding -->
+ <dimen name="focus_rect_widget_outsets">-6dp</dimen>
<!-- Workspace grid visualization parameters -->
<dimen name="grid_visualization_rounding_radius">16dp</dimen>
@@ -464,6 +508,7 @@
<dimen name="bottom_sheet_handle_width">32dp</dimen>
<dimen name="bottom_sheet_handle_height">4dp</dimen>
<dimen name="bottom_sheet_handle_margin">16dp</dimen>
+ <dimen name="widgets_bottom_sheet_handle_margin">24dp</dimen>
<dimen name="bottom_sheet_handle_corner_radius">2dp</dimen>
<!-- State transition -->
@@ -478,18 +523,20 @@
<dimen name="default_ime_height">300dp</dimen>
<!-- Private Space parameters -->
- <dimen name="ps_container_corner_radius">24dp</dimen>
- <dimen name="ps_header_height">64dp</dimen>
+ <dimen name="ps_container_corner_radius">28dp</dimen>
+ <dimen name="ps_header_height">72dp</dimen>
<dimen name="ps_header_relative_layout_height">48dp</dimen>
<dimen name="ps_header_image_height">48dp</dimen>
<dimen name="ps_header_text_height">24dp</dimen>
<dimen name="ps_header_layout_margin">16dp</dimen>
- <dimen name="ps_header_settings_icon_margin_end">8dp</dimen>
+ <dimen name="ps_lock_button_margin_end">12dp</dimen>
<dimen name="ps_header_text_size">16sp</dimen>
<dimen name="ps_button_height">40dp</dimen>
<dimen name="ps_button_width">40dp</dimen>
<dimen name="ps_lock_button_width">89dp</dimen>
- <dimen name="ps_app_divider_padding">16dp</dimen>
+ <dimen name="ps_app_divider_horizontal_padding">16dp</dimen>
+ <dimen name="ps_app_divider_vertical_padding">32dp</dimen>
+ <dimen name="ps_extra_bottom_padding">16dp</dimen>
<dimen name="ps_lock_corner_radius">20dp</dimen>
<dimen name="ps_lock_icon_size">20dp</dimen>
<dimen name="ps_lock_icon_margin_top">10dp</dimen>
@@ -497,6 +544,8 @@
<dimen name="ps_lock_icon_text_margin_start_expanded">8dp</dimen>
<dimen name="ps_lock_icon_text_margin_end_expanded">6dp</dimen>
<dimen name="ps_lock_button_background_padding">10dp</dimen>
+ <dimen name="ps_floating_mask_corner_radius">28dp</dimen>
+ <dimen name="ps_floating_mask_end_padding">16dp</dimen>
<!-- WindowManagerProxy -->
<dimen name="max_width_and_height_of_small_display_cutout">136px</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index 198496f..28496b5 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -19,10 +19,47 @@
<item type="id" name="view_type_widgets_space" />
<item type="id" name="view_type_widgets_list" />
<item type="id" name="view_type_widgets_header" />
- <!-- Used for A11y actions in staged split to identify each task uniquely -->
- <item type="id" name="split_topLeft_appInfo" />
- <item type="id" name="split_bottomRight_appInfo" />
+ <!-- Accessibility actions -->
+ <item type="id" name="action_remove" />
+ <item type="id" name="action_uninstall" />
+ <item type="id" name="action_reconfigure" />
+ <item type="id" name="action_add_to_workspace" />
+ <item type="id" name="action_move" />
+ <item type="id" name="action_move_to_workspace" />
+ <item type="id" name="action_move_screen_backwards" />
+ <item type="id" name="action_move_screen_forwards" />
+ <item type="id" name="action_resize" />
+ <item type="id" name="action_deep_shortcuts" />
+ <item type="id" name="action_remote_action_shortcut" />
+ <item type="id" name="action_dismiss_prediction" />
+ <item type="id" name="action_pin_prediction"/>
+ <item type="id" name="action_close"/>
+ <!-- Used for A11y actions in staged split to identify each task uniquely -->
+ <item type="id" name="action_app_info_top_left" />
+ <item type="id" name="action_app_info_bottom_right" />
+ <item type="id" name="action_digital_wellbeing_top_left" />
+ <item type="id" name="action_digital_wellbeing_bottom_right" />
+
+ <!-- QSB IDs. DO not change -->
+ <item type="id" name="search_container_workspace" />
+ <item type="id" name="search_container_all_apps" />
+ <item type="id" name="search_container_hotseat" />
+
+ <!-- View ID to use for QSB widget -->
+ <item type="id" name="qsb_widget" />
+
+ <!-- View ID used by cell layout to jail its content -->
+ <item type="id" name="cell_layout_jail_id" />
+
+ <!-- View IDs to store item highlight information -->
+ <item type="id" name="view_unhighlight_background" />
+
+ <!-- view ID used to restore work tab state -->
+ <item type="id" name="work_tab_state_id" />
+
+ <!-- Menu id for feature flags -->
+ <item type="id" name="menu_apply_flags" />
<!-- Do not change, must be kept in sync with sysui navbar button IDs for tests! -->
<item type="id" name="home" />
@@ -40,6 +77,7 @@
<item type="id" name="cache_entry_tag_id" />
<item type="id" name="saved_clip_children_tag_id" />
+ <item type="id" name="saved_clip_to_padding_tag_id" />
<item type="id" name="saved_floating_widget_foreground" />
<item type="id" name="saved_floating_widget_background" />
@@ -52,4 +90,13 @@
<item type="id" name="ps_settings_button" />
<item type="id" name="ps_transition_image" />
+ <!-- Recents -->
+ <item type="id" name="overview_panel"/>
+
+ <!-- DragController -->
+ <item type="id" name="drag_event_parity" />
+
+ <!-- View tag key used to store SpringAnimation data. -->
+ <item type="id" name="spring_animation_tag" />
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index aaef15b..d33adc4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -36,11 +36,14 @@
<string name="shortcut_not_available">Shortcut isn\'t available</string>
<!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
<string name="home_screen">Home</string>
+ <!-- Description for setting the current launcher as the default home app. [CHAR_LIMIT=none]-->
+ <string name="set_default_home_app">Set <xliff:g id="launcher_name" example="Launcher3">%1$s</xliff:g> as default home app in Settings</string>
<!-- Options for recent tasks -->
<!-- Title for an option to enter split screen mode for a given app -->
<string name="recent_task_option_split_screen">Split screen</string>
<string name="split_app_info_accessibility">App info for %1$s</string>
+ <string name="split_app_usage_settings">Usage settings for %1$s</string>
<!-- App pairs -->
<string name="save_app_pair">Save app pair</string>
@@ -50,6 +53,8 @@
<string name="app_pair_unlaunchable_at_screen_size">This app pair isn\'t supported on this device</string>
<!-- Displayed when an app pair can't launch at this screen size, but user can unfold device to restore functionality [CHAR_LIMIT=none] -->
<string name="app_pair_needs_unfold">Unfold device to use this app pair</string>
+ <!-- Displayed when user selects a shortcut for an app pair that is currently not available [CHAR_LIMIT=none]-->
+ <string name="app_pair_not_available">App pair isn\'t available</string>
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
@@ -64,6 +69,12 @@
<!-- Spoken text for a screen reader. The placeholder text is the widget name.
[CHAR_LIMIT=none]-->
<string name="widget_preview_context_description"><xliff:g id="widget_name" example="Calendar month view">%1$s</xliff:g> widget</string>
+ <!-- Spoken text for a screen reader. The first placeholder text is the widget name, the
+ remaining placeholders are for the widget dimensions.
+ [CHAR_LIMIT=none]-->
+ <string name="widget_preview_name_and_dims_content_description">
+ <xliff:g id="widget_name" example="Calendar month view">%1$s</xliff:g> widget, %2$d wide by %3$d high
+ </string>
<!-- Message to tell the user to press and hold a widget/icon to add it to the home screen.
[CHAR LIMIT=NONE] -->
<string name="add_item_request_drag_hint">Touch & hold the widget to move it around the home screen</string>
@@ -75,12 +86,21 @@
<!-- Widget suggestions header title in the full widgets picker for large screen devices
in landscape mode. [CHAR_LIMIT=50] -->
<string name="suggested_widgets_header_title">Suggestions</string>
- <string name="productivity_widget_recommendation_category_label">Your Daily Essentials</string>
- <string name="news_widget_recommendation_category_label">News For You</string>
- <string name="social_and_entertainment_widget_recommendation_category_label">Your Chill Zone</string>
- <string name="fitness_widget_recommendation_category_label">Reach Your Fitness Goals</string>
- <string name="weather_widget_recommendation_category_label">Stay Ahead of the Weather</string>
- <string name="others_widget_recommendation_category_label">You Might Also Like</string>
+ <!-- Title for the widget suggestions category that displays widgets provided by
+ productivity apps for daily use [CHAR_LIMIT=50] -->
+ <string name="productivity_widget_recommendation_category_label">Essentials</string>
+ <!-- Title for the widget suggestions category that displays widgets provided by
+ news and magazines related apps [CHAR_LIMIT=50] -->
+ <string name="news_widget_recommendation_category_label">News & magazines</string>
+ <!-- Title for the widget suggestions category that displays widgets provided by
+ entertainment apps [CHAR_LIMIT=50] -->
+ <string name="entertainment_widget_recommendation_category_label">Entertainment</string>
+ <!-- Title for the widget suggestions category that displays widgets provided by
+ social apps [CHAR_LIMIT=50] -->
+ <string name="social_widget_recommendation_category_label">Social</string>
+ <!-- Title for the widget suggestions category that displays general widget suggestions
+ [CHAR_LIMIT=50] -->
+ <string name="others_widget_recommendation_category_label">Suggested for you</string>
<!-- accessibilityPaneTitle for the right pane when showing suggested widgets. -->
<string name="widget_picker_right_pane_accessibility_title"><xliff:g id="selected_header" example="Calendar">%1$s</xliff:g> widgets on right, search and options on left</string>
<!-- Label for showing the number of widgets an app has in the full widgets picker.
@@ -123,20 +143,16 @@
<!-- A widget category label for grouping widgets related to note taking. [CHAR_LIMIT=30] -->
<string name="widget_category_note_taking">Note-taking</string>
- <!-- Title of a dialog. This dialog lets a user know how they can use widgets on their phone.
- [CHAR_LIMIT=NONE] -->
- <string name="widget_education_header">Useful info at your fingertips</string>
- <!-- Dialog text. This dialog lets a user know how they can use widgets on their phone.
- [CHAR_LIMIT=NONE] -->
- <string name="widget_education_content">To get info without opening apps, you can add widgets to your home screen</string>
+ <!-- Text on the button that adds a widget to the home screen. [CHAR_LIMIT=15] -->
+ <string name="widget_add_button_label">Add</string>
+ <!-- Accessibility content description for the button that adds a widget to the home screen. The
+ placeholder text is the widget name. [CHAR_LIMIT=none] -->
+ <string name="widget_add_button_content_description">Add <xliff:g id="widget_name" example="Calendar month view">%1$s</xliff:g> widget</string>
<!-- Text on an educational tip on widget informing users that they can change widget settings.
[CHAR_LIMIT=NONE] -->
<string name="reconfigurable_widget_education_tip">Tap to change widget settings</string>
- <!-- Text on the button that closes the education dialog about widgets. [CHAR_LIMIT=50] -->
- <string name="widget_education_close_button">Got it</string>
-
<!-- Spoken text for screen readers. This text is for an icon that lets the user change a
widget's settings. [CHAR_LIMIT=50] -->
<string name="widget_reconfigure_button_content_description">Change widget settings</string>
@@ -185,6 +201,8 @@
<string name="app_info_drop_target_label">App info</string>
<!-- Label for install to private profile shortcut label. [CHAR_LIMIT=20] -->
<string name="install_private_system_shortcut_label">Install in private</string>
+ <!-- Label for uninstall app private profile shortcut.-->
+ <string name="uninstall_private_system_shortcut_label">Uninstall app</string>
<!-- Label for install drop target. [CHAR_LIMIT=20] -->
<string name="install_drop_target_label">Install</string>
<!-- Label for dismiss prediction. -->
@@ -327,7 +345,7 @@
<!-- Title for an app whose download has been started. -->
<string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
<!-- Title for an app which is archived. -->
- <string name="app_archived_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> is archived. Tap to download.</string>
+ <string name="app_archived_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> is archived. Tap to download and restore.</string>
<!-- Title shown on the alert dialog prompting the user to update the application in market
@@ -435,6 +453,8 @@
<string name="work_profile_edu_work_apps">Work apps are badged and visible to your IT admin</string>
<!-- Action label to finish work profile edu-->
<string name="work_profile_edu_accept">Got it</string>
+ <!-- Info icon unicode for alpha scroller when work edu card is present -->
+ <string name="work_profile_edu_section" translatable="false">\u24D8</string>
<!--- heading shown when user opens work apps tab while work apps are paused -->
<string name="work_apps_paused_title">Work apps are paused</string>
@@ -464,22 +484,20 @@
<!-- Private space tile label -->
<string name="private_space_label">Private space</string>
<!-- Private space tile secondary label -->
- <string name="private_space_secondary_label">Keep private apps locked and hidden</string>
+ <string name="private_space_secondary_label">Tap to set up or open</string>
<!-- Title for Private Space Container shown at the bottom of all apps drawer -->
<string name="ps_container_title">Private</string>
<!-- Description for Private Space Settings button -->
<string name="ps_container_settings">Private Space Settings</string>
- <!-- Description for Private Space Lock/Unlock button -->
- <string name="ps_container_lock_unlock_button">Lock/Unlock Private Space</string>
+ <!-- Description for Private Space Unlock button -->
+ <string name="ps_container_unlock_button_content_description">Private, unlocked.</string>
+ <!-- Description for Private Space Lock button -->
+ <string name="ps_container_lock_button_content_description">Private, locked.</string>
<string name="ps_container_lock_title">Lock</string>
<!-- Description for Private Space Transition button -->
<string name="ps_container_transition">Private Space Transitioning</string>
<!-- Title for Private Space install app icon -->
- <string name="ps_add_button_label">Install apps</string>
+ <string name="ps_add_button_label">Install</string>
<!-- Content description for install app icon -->
<string name="ps_add_button_content_description">Install apps to Private Space</string>
-
- <!-- Strings for bubble bar -->
- <!-- content description for the overflow bubble [CHAR_LIMIT=none] -->
- <string name="bubble_bar_overflow_description">Overflow</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 401155d..ae3d3b3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -52,14 +52,16 @@
<item name="workspaceAmbientShadowColor">#40000000</item>
<item name="workspaceKeyShadowColor">#89000000</item>
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
- <item name="focusOutlineColor">@color/material_color_on_secondary_container</item>
- <item name="folderPaginationColor">@color/folder_pagination_color_light</item>
+ <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_light</item>
+ <item name="focusOutlineColor">@color/material_color_secondary_fixed</item>
+ <item name="focusInnerOutlineColor">@color/material_color_on_secondary_fixed_variant</item>
<item name="folderPreviewColor">@color/folder_preview_light</item>
<item name="folderBackgroundColor">@color/folder_background_light</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
<item name="isFolderDarkText">true</item>
<item name="folderTextColor">@color/folder_text_color_light</item>
<item name="folderHintTextColor">@color/folder_hint_text_color_light</item>
+ <item name="appPairSurfaceInFolder">@color/material_color_surface_container_lowest</item>
<item name="loadingIconColor">#CCFFFFFF</item>
<item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
<item name="eduHalfSheetBGColor">?android:attr/colorAccent</item>
@@ -164,13 +166,14 @@
<item name="popupShadeThird">@color/popup_shade_third_dark</item>
<item name="notificationDotColor">@color/notification_dot_color_dark</item>
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
- <item name="folderPaginationColor">@color/folder_pagination_color_dark</item>
+ <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_dark</item>
<item name="folderPreviewColor">@color/folder_preview_dark</item>
<item name="folderBackgroundColor">@color/folder_background_dark</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
<item name="isFolderDarkText">false</item>
<item name="folderTextColor">@color/folder_text_color_dark</item>
<item name="folderHintTextColor">@color/folder_hint_text_color_dark</item>
+ <item name="appPairSurfaceInFolder">@color/material_color_surface_container_lowest</item>
<item name="isMainColorDark">true</item>
<item name="loadingIconColor">#99FFFFFF</item>
<item name="iconOnlyShortcutColor">#B3FFFFFF</item>
@@ -261,6 +264,14 @@
@color/widget_picker_unselected_tab_text_color_light</item>
<item name="widgetPickerCollapseHandleColor">
@color/widget_picker_collapse_handle_color_light</item>
+ <item name="widgetPickerAddButtonBackgroundColor">
+ @color/widget_picker_add_button_background_color_light</item>
+ <item name="widgetPickerAddButtonTextColor">
+ @color/widget_picker_add_button_text_color_light</item>
+ <item name="widgetCellTitleColor">
+ @color/widget_cell_title_color_light</item>
+ <item name="widgetCellSubtitleColor">
+ @color/widget_cell_subtitle_color_light</item>
</style>
<style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
<item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
@@ -292,6 +303,14 @@
@color/widget_picker_unselected_tab_text_color_dark</item>
<item name="widgetPickerCollapseHandleColor">
@color/widget_picker_collapse_handle_color_dark</item>
+ <item name="widgetPickerAddButtonBackgroundColor">
+ @color/widget_picker_add_button_background_color_dark</item>
+ <item name="widgetPickerAddButtonTextColor">
+ @color/widget_picker_add_button_text_color_dark</item>
+ <item name="widgetCellTitleColor">
+ @color/widget_cell_title_color_dark</item>
+ <item name="widgetCellSubtitleColor">
+ @color/widget_cell_subtitle_color_dark</item>
</style>
<style name="FastScrollerPopup" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
@@ -408,6 +427,9 @@
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowTranslucentStatus">true</item>
+ <!-- Add the dim background here, rather than in the activity layout as the window slides
+ in from the bottom, and we don't want the scrim to slide. -->
+ <item name="android:backgroundDimEnabled">true</item>
</style>
<style name="ProxyActivityStarterTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index c9a44a1..1d0dbff 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -168,7 +168,6 @@
launcher:numFolderRows="3"
launcher:numFolderColumns="3"
launcher:numHotseatIcons="6"
- launcher:hotseatColumnSpanLandscape="4"
launcher:numAllAppsColumns="6"
launcher:isScalable="true"
launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_6_5"
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index ce13bfb..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,5 +0,0 @@
-include ':IconLoader'
-project(':IconLoader').projectDir = new File(rootDir, 'iconloaderlib')
-
-include ':SharedLibWrapper'
-project(':SharedLibWrapper').projectDir = new File(rootDir, 'SharedLibWrapper')
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index e7b88dc..876b643 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -70,7 +70,6 @@
TYPE_ICON_SURFACE,
TYPE_OPTIONS_POPUP_DIALOG,
TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP,
- TYPE_WIDGETS_EDUCATION_DIALOG,
TYPE_TASKBAR_EDUCATION_DIALOG,
TYPE_TASKBAR_ALL_APPS,
TYPE_ADD_TO_HOME_CONFIRMATION,
@@ -98,7 +97,6 @@
public static final int TYPE_OPTIONS_POPUP_DIALOG = 1 << 14;
public static final int TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP = 1 << 15;
- public static final int TYPE_WIDGETS_EDUCATION_DIALOG = 1 << 16;
public static final int TYPE_TASKBAR_EDUCATION_DIALOG = 1 << 17;
public static final int TYPE_TASKBAR_ALL_APPS = 1 << 18;
public static final int TYPE_ADD_TO_HOME_CONFIRMATION = 1 << 19;
@@ -111,16 +109,16 @@
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
| TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
| TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP
- | TYPE_WIDGETS_EDUCATION_DIALOG | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS
- | TYPE_OPTIONS_POPUP_DIALOG | TYPE_ADD_TO_HOME_CONFIRMATION
- | TYPE_TASKBAR_OVERLAY_PROXY | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP;
+ | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG
+ | TYPE_ADD_TO_HOME_CONFIRMATION | TYPE_TASKBAR_OVERLAY_PROXY
+ | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
- | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_WIDGETS_EDUCATION_DIALOG
- | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG
- | TYPE_TASKBAR_OVERLAY_PROXY | TYPE_PIN_IME_POPUP;
+ | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_TASKBAR_EDUCATION_DIALOG
+ | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG | TYPE_TASKBAR_OVERLAY_PROXY
+ | TYPE_PIN_IME_POPUP;
/** Type of popups that should get exclusive accessibility focus. */
public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
@@ -278,18 +276,7 @@
public static void closeOpenViews(ActivityContext activity, boolean animate,
@FloatingViewType int type) {
- BaseDragLayer dragLayer = activity.getDragLayer();
- // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
- // and will be one of the last views.
- for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
- View child = dragLayer.getChildAt(i);
- if (child instanceof AbstractFloatingView) {
- AbstractFloatingView abs = (AbstractFloatingView) child;
- if (abs.isOfType(type)) {
- abs.close(animate);
- }
- }
- }
+ new AbstractFloatingViewHelper().closeOpenViews(activity, animate, type);
}
public static void closeAllOpenViews(ActivityContext activity, boolean animate) {
diff --git a/src/com/android/launcher3/AbstractFloatingViewHelper.kt b/src/com/android/launcher3/AbstractFloatingViewHelper.kt
new file mode 100644
index 0000000..0bfbc6e
--- /dev/null
+++ b/src/com/android/launcher3/AbstractFloatingViewHelper.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import com.android.launcher3.AbstractFloatingView.FloatingViewType
+import com.android.launcher3.views.ActivityContext
+
+/**
+ * Helper class for manaing AbstractFloatingViews which shows a floating UI on top of the launcher
+ * UI.
+ */
+class AbstractFloatingViewHelper {
+ fun closeOpenViews(activity: ActivityContext, animate: Boolean, @FloatingViewType type: Int) {
+ val dragLayer = activity.getDragLayer()
+ // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
+ // and will be one of the last views.
+ for (i in dragLayer.getChildCount() - 1 downTo 0) {
+ val child = dragLayer.getChildAt(i)
+ if (child is AbstractFloatingView && child.isOfType(type)) {
+ child.close(animate)
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 4a277f0..b51e850 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -46,6 +46,7 @@
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.util.WidgetSizes;
import java.util.ArrayList;
@@ -135,6 +136,7 @@
private final Rect mWidgetViewNewRect = new Rect();
private final @Nullable LauncherAppWidgetHostView.CellChildViewPreLayoutListener
mCellChildViewPreLayoutListener;
+ private final @NonNull OnLayoutChangeListener mWidgetViewLayoutListener;
private int mXDown, mYDown;
@@ -176,6 +178,9 @@
mDragAcrossTwoPanelOpacityMargin = mLauncher.getResources().getDimensionPixelSize(
R.dimen.resize_frame_invalid_drag_across_two_panel_opacity_margin);
mDragLayerRelativeCoordinateHelper = new ViewGroupFocusHelper(mLauncher.getDragLayer());
+
+ mWidgetViewLayoutListener =
+ (v, l, t, r, b, oldL, oldT, oldR, oldB) -> setCornerRadiusFromWidget();
}
@Override
@@ -200,21 +205,16 @@
}
public static void showForWidget(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
+ // If widget is not added to view hierarchy, we cannot show resize frame at correct location
+ if (widget.getParent() == null) {
+ return;
+ }
Launcher launcher = Launcher.getLauncher(cellLayout.getContext());
AbstractFloatingView.closeAllOpenViews(launcher);
DragLayer dl = launcher.getDragLayer();
AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
.inflate(R.layout.app_widget_resize_frame, dl, false);
- if (widget.hasEnforcedCornerRadius()) {
- float enforcedCornerRadius = widget.getEnforcedCornerRadius();
- ImageView imageView = frame.findViewById(R.id.widget_resize_frame);
- Drawable d = imageView.getDrawable();
- if (d instanceof GradientDrawable) {
- GradientDrawable gd = (GradientDrawable) d.mutate();
- gd.setCornerRadius(enforcedCornerRadius);
- }
- }
frame.setupForWidget(widget, cellLayout, dl);
((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
@@ -223,6 +223,18 @@
frame.post(() -> frame.snapToWidget(false));
}
+ private void setCornerRadiusFromWidget() {
+ if (mWidgetView != null && mWidgetView.hasEnforcedCornerRadius()) {
+ float enforcedCornerRadius = mWidgetView.getEnforcedCornerRadius();
+ ImageView imageView = findViewById(R.id.widget_resize_frame);
+ Drawable d = imageView.getDrawable();
+ if (d instanceof GradientDrawable) {
+ GradientDrawable gd = (GradientDrawable) d.mutate();
+ gd.setCornerRadius(enforcedCornerRadius);
+ }
+ }
+ }
+
private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
DragLayer dragLayer) {
mCellLayout = cellLayout;
@@ -312,6 +324,9 @@
.log(LAUNCHER_WIDGET_RESIZE_STARTED);
setOnKeyListener(this);
+
+ setCornerRadiusFromWidget();
+ mWidgetView.addOnLayoutChangeListener(mWidgetViewLayoutListener);
}
public boolean beginResizeIfPointInRegion(int x, int y) {
@@ -470,8 +485,11 @@
mLastDirectionVector[1] = mDirectionVector[1];
}
- if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
- mDirectionVector, onDismiss)) {
+ // We don't want to evaluate resize if a widget was pending config activity and was already
+ // occupying a space on the screen. This otherwise will cause reorder algorithm evaluate a
+ // different location for the widget and cause a jump.
+ if (!(mWidgetView instanceof PendingAppWidgetHostView) && mCellLayout.createAreaForResize(
+ cellX, cellY, spanX, spanY, mWidgetView, mDirectionVector, onDismiss)) {
if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY) ) {
mStateAnnouncer.announce(
mLauncher.getString(R.string.widget_resized, spanX, spanY));
@@ -721,6 +739,7 @@
mWidgetView.setLayoutTransition(null);
}
mDragLayer.removeView(this);
+ mWidgetView.removeOnLayoutChangeListener(mWidgetViewLayoutListener);
}
private void updateInvalidResizeEffect(CellLayout cellLayout, CellLayout pairedCellLayout,
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index c7cdfa8..175d6ec 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -19,6 +19,8 @@
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
+import static com.android.launcher3.util.UserIconInfo.TYPE_CLONED;
+import static com.android.launcher3.util.UserIconInfo.TYPE_WORK;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -34,6 +36,7 @@
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.os.Process;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
@@ -52,10 +55,11 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.Partner;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.UserIconInfo;
import com.android.launcher3.widget.LauncherWidgetHolder;
import org.xmlpull.v1.XmlPullParser;
@@ -63,6 +67,7 @@
import java.io.IOException;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;
@@ -125,34 +130,38 @@
private static final String TAG_INCLUDE = "include";
public static final String TAG_WORKSPACE = "workspace";
private static final String TAG_APP_ICON = "appicon";
- private static final String TAG_AUTO_INSTALL = "autoinstall";
- private static final String TAG_FOLDER = "folder";
- private static final String TAG_APPWIDGET = "appwidget";
+ public static final String TAG_AUTO_INSTALL = "autoinstall";
+ public static final String TAG_FOLDER = "folder";
+ public static final String TAG_APPWIDGET = "appwidget";
protected static final String TAG_SEARCH_WIDGET = "searchwidget";
- private static final String TAG_SHORTCUT = "shortcut";
+ public static final String TAG_SHORTCUT = "shortcut";
private static final String TAG_EXTRA = "extra";
- private static final String ATTR_CONTAINER = "container";
- private static final String ATTR_RANK = "rank";
+ public static final String ATTR_CONTAINER = "container";
+ public static final String ATTR_RANK = "rank";
- private static final String ATTR_PACKAGE_NAME = "packageName";
- private static final String ATTR_CLASS_NAME = "className";
- private static final String ATTR_TITLE = "title";
- private static final String ATTR_TITLE_TEXT = "titleText";
- private static final String ATTR_SCREEN = "screen";
- private static final String ATTR_SHORTCUT_ID = "shortcutId";
+ public static final String ATTR_PACKAGE_NAME = "packageName";
+ public static final String ATTR_CLASS_NAME = "className";
+ public static final String ATTR_TITLE = "title";
+ public static final String ATTR_TITLE_TEXT = "titleText";
+ public static final String ATTR_SCREEN = "screen";
+ public static final String ATTR_SHORTCUT_ID = "shortcutId";
// x and y can be specified as negative integers, in which case -1 represents the
// last row / column, -2 represents the second last, and so on.
- private static final String ATTR_X = "x";
- private static final String ATTR_Y = "y";
+ public static final String ATTR_X = "x";
+ public static final String ATTR_Y = "y";
- private static final String ATTR_SPAN_X = "spanX";
- private static final String ATTR_SPAN_Y = "spanY";
+ public static final String ATTR_SPAN_X = "spanX";
+ public static final String ATTR_SPAN_Y = "spanY";
// Attrs for "Include"
private static final String ATTR_WORKSPACE = "workspace";
+ public static final String ATTR_USER_TYPE = "userType";
+ public static final String USER_TYPE_WORK = "work";
+ public static final String USER_TYPE_CLONED = "cloned";
+
// Style attrs -- "Extra"
private static final String ATTR_KEY = "key";
private static final String ATTR_VALUE = "value";
@@ -168,6 +177,8 @@
protected final SourceResources mSourceRes;
protected final Supplier<XmlPullParser> mInitialLayoutSupplier;
+ private final Map<String, Long> mUserTypeToSerial;
+
private final InvariantDeviceProfile mIdp;
private final int mRowCount;
private final int mColumnCount;
@@ -203,16 +214,26 @@
mIdp = LauncherAppState.getIDP(context);
mRowCount = mIdp.numRows;
mColumnCount = mIdp.numColumns;
- mActivityOverride = ApiWrapper.getActivityOverrides(context);
+ mActivityOverride = ApiWrapper.INSTANCE.get(context).getActivityOverrides();
+
+ mUserTypeToSerial = new HashMap<>();
+ UserCache cache = UserCache.getInstance(context);
+ for (UserHandle user : cache.getUserProfiles()) {
+ UserIconInfo uii = cache.getUserInfo(user);
+ switch (uii.type) {
+ case TYPE_WORK -> mUserTypeToSerial.put(USER_TYPE_WORK, uii.userSerial);
+ case TYPE_CLONED -> mUserTypeToSerial.put(USER_TYPE_CLONED, uii.userSerial);
+ }
+ }
}
/**
* Loads the layout in the db and returns the number of entries added on the desktop.
*/
- public int loadLayout(SQLiteDatabase db, IntArray screenIds) {
+ public int loadLayout(SQLiteDatabase db) {
mDb = db;
try {
- return parseLayout(mInitialLayoutSupplier.get(), screenIds);
+ return parseLayout(mInitialLayoutSupplier.get());
} catch (Exception e) {
Log.e(TAG, "Error parsing layout: ", e);
return -1;
@@ -222,7 +243,7 @@
/**
* Parses the layout and returns the number of elements added on the homescreen.
*/
- protected int parseLayout(XmlPullParser parser, IntArray screenIds)
+ protected int parseLayout(XmlPullParser parser)
throws XmlPullParserException, IOException {
beginDocument(parser, mRootTag);
final int depth = parser.getDepth();
@@ -235,7 +256,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- count += parseAndAddNode(parser, tagParserMap, screenIds);
+ count += parseAndAddNode(parser, tagParserMap);
}
return count;
}
@@ -259,14 +280,14 @@
* Parses the current node and returns the number of elements added.
*/
protected int parseAndAddNode(
- XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
+ XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap)
throws XmlPullParserException, IOException {
if (TAG_INCLUDE.equals(parser.getName())) {
final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
if (resId != 0) {
// recursively load some more favorites, why not?
- return parseLayout(mSourceRes.getXml(resId), screenIds);
+ return parseLayout(mSourceRes.getXml(resId));
} else {
return 0;
}
@@ -284,22 +305,17 @@
convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount));
mValues.put(Favorites.CELLY,
convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount));
+ Long profileId = mUserTypeToSerial.get(getAttributeValue(parser, ATTR_USER_TYPE));
+ if (profileId != null) {
+ mValues.put(Favorites.PROFILE_ID, profileId);
+ }
TagParser tagParser = tagParserMap.get(parser.getName());
if (tagParser == null) {
if (LOGD) Log.d(TAG, "Ignoring unknown element tag: " + parser.getName());
return 0;
}
- int newElementId = tagParser.parseAndAdd(parser);
- if (newElementId >= 0) {
- // Keep track of the set of screens which need to be added to the db.
- if (!screenIds.contains(screenId) &&
- container == Favorites.CONTAINER_DESKTOP) {
- screenIds.add(screenId);
- }
- return 1;
- }
- return 0;
+ return tagParser.parseAndAdd(parser) >= 0 ? 1 : 0;
}
protected int addShortcut(String title, Intent intent, int type) {
@@ -311,10 +327,11 @@
mValues.put(Favorites.SPANY, 1);
mValues.put(Favorites._ID, id);
- if (type == ITEM_TYPE_APPLICATION) {
- ComponentName cn = intent.getComponent();
- if (cn != null && mActivityOverride.containsKey(cn.getPackageName())) {
- LauncherActivityInfo replacementInfo = mActivityOverride.get(cn.getPackageName());
+ ComponentName cn = intent.getComponent();
+ if (cn != null && type == ITEM_TYPE_APPLICATION
+ && !mValues.containsKey(Favorites.PROFILE_ID)) {
+ LauncherActivityInfo replacementInfo = mActivityOverride.get(cn.getPackageName());
+ if (replacementInfo != null) {
mValues.put(Favorites.PROFILE_ID, UserCache.INSTANCE.get(mContext)
.getSerialNumberForUser(replacementInfo.getUser()));
mValues.put(Favorites.INTENT, AppInfo.makeLaunchIntent(replacementInfo).toUri(0));
@@ -420,11 +437,7 @@
}
mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON);
- final Intent intent = new Intent(Intent.ACTION_MAIN, null)
- .addCategory(Intent.CATEGORY_LAUNCHER)
- .setComponent(new ComponentName(packageName, className))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ Intent intent = AppInfo.makeLaunchIntent(new ComponentName(packageName, className));
return addShortcut(mContext.getString(R.string.package_state_unknown), intent,
ITEM_TYPE_APPLICATION);
}
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 1049314..633091d 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -412,6 +412,8 @@
public static <T extends BaseActivity> T fromContext(Context context) {
if (context instanceof BaseActivity) {
return (T) context;
+ } else if (context instanceof ActivityContextDelegate) {
+ return (T) ((ActivityContextDelegate) context).mDelegate;
} else if (context instanceof ContextWrapper) {
return fromContext(((ContextWrapper) context).getBaseContext());
} else {
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 1c2ed43..8585b66 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -111,8 +111,6 @@
return false;
}
- public abstract <T extends View> T getOverviewPanel();
-
public abstract View getRootView();
public void returnToHomescreen() {
@@ -149,7 +147,8 @@
@Override
public void onDisplayInfoChanged(Context context, Info info, int flags) {
- if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.updateIsSeascape(this)) {
+ if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) {
+ mDeviceProfile.updateIsSeascape(this);
reapplyUi();
}
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 3ae1ce1..7d09164 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -46,6 +46,7 @@
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.Property;
+import android.util.Size;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -58,7 +59,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
import com.android.launcher3.dragndrop.DraggableView;
@@ -96,10 +96,10 @@
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
IconLabelDotView, DraggableView, Reorderable {
- private static final int DISPLAY_WORKSPACE = 0;
+ public static final int DISPLAY_WORKSPACE = 0;
public static final int DISPLAY_ALL_APPS = 1;
- private static final int DISPLAY_FOLDER = 2;
- protected static final int DISPLAY_TASKBAR = 5;
+ public static final int DISPLAY_FOLDER = 2;
+ public static final int DISPLAY_TASKBAR = 5;
public static final int DISPLAY_SEARCH_RESULT = 6;
public static final int DISPLAY_SEARCH_RESULT_SMALL = 7;
public static final int DISPLAY_PREDICTION_ROW = 8;
@@ -183,6 +183,24 @@
private Animator mDotScaleAnim;
private boolean mForceHideDot;
+ // These fields, related to showing running apps, are only used for Taskbar.
+ private final Size mRunningAppIndicatorSize;
+ private final int mRunningAppIndicatorTopMargin;
+ private final Size mMinimizedAppIndicatorSize;
+ private final int mMinimizedAppIndicatorTopMargin;
+ private final Paint mRunningAppIndicatorPaint;
+ private final Rect mRunningAppIconBounds = new Rect();
+ private RunningAppState mRunningAppState;
+
+ /**
+ * Various options for the running state of an app.
+ */
+ public enum RunningAppState {
+ NOT_RUNNING,
+ RUNNING,
+ MINIMIZED,
+ }
+
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mStayPressed;
@ViewDebug.ExportedProperty(category = "launcher")
@@ -238,7 +256,7 @@
defaultIconSize = getResources().getDimensionPixelSize(
R.dimen.search_row_small_icon_size);
} else if (mDisplay == DISPLAY_TASKBAR) {
- defaultIconSize = mDeviceProfile.iconSizePx;
+ defaultIconSize = mDeviceProfile.taskbarIconSize;
} else {
// widget_selection or shortcut_popup
defaultIconSize = mDeviceProfile.iconSizePx;
@@ -249,6 +267,23 @@
defaultIconSize);
a.recycle();
+ mRunningAppIndicatorSize = new Size(
+ getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width),
+ getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_height));
+ mMinimizedAppIndicatorSize = new Size(
+ getResources().getDimensionPixelSize(R.dimen.taskbar_minimized_app_indicator_width),
+ getResources().getDimensionPixelSize(
+ R.dimen.taskbar_minimized_app_indicator_height));
+ mRunningAppIndicatorTopMargin =
+ getResources().getDimensionPixelSize(
+ R.dimen.taskbar_running_app_indicator_top_margin);
+ mMinimizedAppIndicatorTopMargin =
+ getResources().getDimensionPixelSize(
+ R.dimen.taskbar_minimized_app_indicator_top_margin);
+ mRunningAppIndicatorPaint = new Paint();
+ mRunningAppIndicatorPaint.setColor(getResources().getColor(
+ R.color.taskbar_running_app_indicator_color, context.getTheme()));
+
mLongPressHelper = new CheckLongPressHelper(this);
mDotParams = new DotRenderer.DrawParams();
@@ -395,6 +430,12 @@
setDownloadStateContentDescription(info, info.getProgressLevel());
}
+ /** Updates whether the app this view represents is currently running. */
+ @UiThread
+ public void updateRunningState(RunningAppState runningAppState) {
+ mRunningAppState = runningAppState;
+ }
+
protected void setItemInfo(ItemInfoWithIcon itemInfo) {
setTag(itemInfo);
}
@@ -436,8 +477,7 @@
}
@UiThread
- @VisibleForTesting
- public void applyLabel(ItemInfoWithIcon info) {
+ public void applyLabel(ItemInfo info) {
CharSequence label = info.title;
if (label != null) {
mLastOriginalText = label;
@@ -622,6 +662,7 @@
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawDotIfNecessary(canvas);
+ drawRunningAppIndicatorIfNecessary(canvas);
}
/**
@@ -633,7 +674,7 @@
if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
getIconBounds(mDotParams.iconBounds);
Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
- IconShape.getNormalizationScale());
+ IconShape.INSTANCE.get(getContext()).getNormalizationScale());
final int scrollX = getScrollX();
final int scrollY = getScrollY();
canvas.translate(scrollX, scrollY);
@@ -642,6 +683,24 @@
}
}
+ /** Draws a line under the app icon if this is representing a running app in Desktop Mode. */
+ protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) {
+ if (mRunningAppState == RunningAppState.NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) {
+ return;
+ }
+ getIconBounds(mRunningAppIconBounds);
+ // TODO(b/333872717): update color, shape, and size of indicator
+ boolean isMinimized = mRunningAppState == RunningAppState.MINIMIZED;
+ int indicatorTop =
+ mRunningAppIconBounds.bottom + (isMinimized ? mMinimizedAppIndicatorTopMargin
+ : mRunningAppIndicatorTopMargin);
+ final Size indicatorSize =
+ isMinimized ? mMinimizedAppIndicatorSize : mRunningAppIndicatorSize;
+ canvas.drawRect(mRunningAppIconBounds.centerX() - indicatorSize.getWidth() / 2,
+ indicatorTop, mRunningAppIconBounds.centerX() + indicatorSize.getWidth() / 2,
+ indicatorTop + indicatorSize.getHeight(), mRunningAppIndicatorPaint);
+ }
+
@Override
public void setForceHideDot(boolean forceHideDot) {
if (mForceHideDot == forceHideDot) {
@@ -913,7 +972,8 @@
/** Applies the given progress level to the this icon's progress bar. */
@Nullable
public PreloadIconDrawable applyProgressLevel() {
- if (!(getTag() instanceof ItemInfoWithIcon)) {
+ if (!(getTag() instanceof ItemInfoWithIcon)
+ || ((ItemInfoWithIcon) getTag()).isInactiveArchive()) {
return null;
}
@@ -970,6 +1030,7 @@
return info.isDisabled() || info.isPendingDownload();
}
+
public void applyDotState(ItemInfo itemInfo, boolean animate) {
if (mIcon instanceof FastBitmapDrawable) {
boolean wasDotted = mDotInfo != null;
@@ -1230,4 +1291,13 @@
public boolean canShowLongPressPopup() {
return getTag() instanceof ItemInfo && ShortcutUtil.supportsShortcuts((ItemInfo) getTag());
}
+
+ /** Returns the package name of the app this icon represents. */
+ public String getTargetPackageName() {
+ Object tag = getTag();
+ if (tag instanceof ItemInfo itemInfo) {
+ return itemInfo.getTargetPackage();
+ }
+ return null;
+ }
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 72758f2..7e9e864 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -33,7 +33,6 @@
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
@@ -115,7 +114,6 @@
@Thunk final int[] mTempLocation = new int[2];
@Thunk final Rect mTempOnDrawCellToRect = new Rect();
- final PointF mTmpPointF = new PointF();
protected GridOccupancy mOccupied;
public GridOccupancy mTmpOccupied;
@@ -123,7 +121,7 @@
private OnTouchListener mInterceptTouchListener;
private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
- final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
+ final PreviewBackground mFolderLeaveBehind = new PreviewBackground(getContext());
private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
private static final int[] BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET;
@@ -197,16 +195,11 @@
public static final int REORDER_ANIMATION_DURATION = 150;
@Thunk final float mReorderPreviewAnimationMagnitude;
- private final ArrayList<View> mIntersectingViews = new ArrayList<>();
- private final Rect mOccupiedRect = new Rect();
public final int[] mDirectionVector = new int[2];
ItemConfiguration mPreviousSolution = null;
- private static final int INVALID_DIRECTION = -100;
private final Rect mTempRect = new Rect();
- private final RectF mTempRectF = new RectF();
- private final float[] mTmpFloatArray = new float[4];
private static final Paint sPaint = new Paint();
@@ -1163,9 +1156,6 @@
mDragCellSpan[0] = spanX;
mDragCellSpan[1] = spanY;
- // Apply color extraction on a widget when dragging.
- applyColorExtractionOnWidget(dragObject, mDragCell, spanX, spanY);
-
final int oldIndex = mDragOutlineCurrent;
mDragOutlineAnims[oldIndex].animateOut();
mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
@@ -1186,19 +1176,6 @@
}
}
- /** Applies the local color extraction to a dragging widget object. */
- private void applyColorExtractionOnWidget(DropTarget.DragObject dragObject, int[] targetCell,
- int spanX, int spanY) {
- // Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
- View view = dragObject.dragView.getContentView();
- if (view instanceof LauncherAppWidgetHostView) {
- int screenId = mCellLayoutContainer.getCellLayoutId(this);
- cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
-
- ((LauncherAppWidgetHostView) view).handleDrag(mTempRect, this, screenId);
- }
- }
-
@SuppressLint("StringFormatMatches")
public String getItemMoveDescription(int cellX, int cellY) {
if (mContainerType == HOTSEAT) {
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 9a5627a..58789fd 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -27,7 +27,7 @@
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -75,7 +75,7 @@
}
return (info instanceof LauncherAppWidgetInfo)
- || (info instanceof FolderInfo);
+ || (info instanceof CollectionInfo);
}
@Override
diff --git a/src/com/android/launcher3/DevicePaddings.java b/src/com/android/launcher3/DevicePaddings.java
index 08fb47b..8494d11 100644
--- a/src/com/android/launcher3/DevicePaddings.java
+++ b/src/com/android/launcher3/DevicePaddings.java
@@ -47,7 +47,7 @@
private static final String WORKSPACE_BOTTOM_PADDING = "workspaceBottomPadding";
private static final String HOTSEAT_BOTTOM_PADDING = "hotseatBottomPadding";
- private static final String TAG = DevicePaddings.class.getSimpleName();
+ private static final String TAG = "DevicePaddings";
private static final boolean DEBUG = false;
ArrayList<DevicePadding> mDevicePaddings = new ArrayList<>();
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f96d59e..00db3a3 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -18,6 +18,7 @@
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Flags.enableOverviewIconMenu;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT;
import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE;
import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
@@ -31,6 +32,7 @@
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat;
+import static com.android.wm.shell.Flags.enableTinyTaskbar;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -46,11 +48,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.ResourcesCompat;
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.DevicePaddings.DevicePadding;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.model.data.ItemInfo;
@@ -62,13 +64,13 @@
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType;
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType;
import com.android.launcher3.responsive.ResponsiveSpecsProvider;
-import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.CellContentDimensions;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.IconSizeSteps;
import com.android.launcher3.util.ResourceHelper;
import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.WindowManagerProxy;
import java.io.PrintWriter;
import java.util.Locale;
@@ -101,6 +103,7 @@
public final boolean transposeLayoutWithOrientation;
public final boolean isMultiDisplay;
public final boolean isTwoPanels;
+ public boolean isPredictiveBackSwipe;
public final boolean isQsbInline;
// Device properties in current orientation
@@ -210,6 +213,8 @@
// Hotseat
public int numShownHotseatIcons;
public int hotseatCellHeightPx;
+ private int mHotseatColumnSpan;
+ private int mHotseatWidthPx; // not used in vertical bar layout
public final boolean areNavButtonsInline;
// In portrait: size = height, in landscape: size = width
public int hotseatBarSizePx;
@@ -348,7 +353,8 @@
isTablet = info.isTablet(windowBounds);
isPhone = !isTablet;
isTwoPanels = isTablet && isMultiDisplay;
- isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS;
+ isTaskbarPresent = (isTablet || (enableTinyTaskbar() && isGestureMode))
+ && WindowManagerProxy.INSTANCE.get(context).isTaskbarDrawnInProcess();
// Some more constants.
context = getContext(context, info, isVerticalBarLayout() || (isTablet && isLandscape)
@@ -436,7 +442,11 @@
if (isMultiDisplay && !ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH.get()) {
// TODO(b/259893832): Revert to use maxWallpaperScale to calculate bottomSheetDepth
// when screen recorder bug is fixed.
- bottomSheetDepth = 1f;
+ if (enableScalingRevealHomeAnimation()) {
+ bottomSheetDepth = 0.3f;
+ } else {
+ bottomSheetDepth = 1f;
+ }
} else {
// The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps.
// When depth is 0, wallpaper zoom is set to maxWallpaperScale.
@@ -544,6 +554,7 @@
areNavButtonsInline = isTaskbarPresent && !isGestureMode;
numShownHotseatIcons =
isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
+ mHotseatColumnSpan = inv.numColumns;
numShownAllAppsColumns =
isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns;
@@ -702,7 +713,7 @@
overviewTaskThumbnailTopMarginPx =
enableOverviewIconMenu() ? 0 : overviewTaskIconSizePx + overviewTaskMarginPx;
// Don't add margin with floating search bar to minimize risk of overlapping.
- overviewActionsTopMarginPx = FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() ? 0
+ overviewActionsTopMarginPx = Flags.floatingSearchBar() ? 0
: res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
overviewActionsButtonSpacing = res.getDimensionPixelSize(
@@ -811,8 +822,7 @@
- hotseatBorderSpace * numShownHotseatIcons
- iconExtraSpacePx;
} else {
- int columns = inv.hotseatColumnSpan[mTypeIndex];
- return getIconToIconWidthForColumns(columns) - iconExtraSpacePx;
+ return getIconToIconWidthForColumns(mHotseatColumnSpan) - iconExtraSpacePx;
}
}
@@ -883,10 +893,31 @@
public void recalculateHotseatWidthAndBorderSpace() {
if (!mIsScalableGrid) return;
- int columns = inv.hotseatColumnSpan[mTypeIndex];
- float hotseatWidthPx = getIconToIconWidthForColumns(columns);
- hotseatBorderSpace = calculateHotseatBorderSpace(hotseatWidthPx, /* numExtraBorder= */ 0);
+ updateHotseatWidthAndBorderSpace(inv.numColumns);
+ int numWorkspaceColumns = getPanelCount() * inv.numColumns;
+ if (isTwoPanels) {
+ updateHotseatWidthAndBorderSpace(inv.numDatabaseHotseatIcons);
+ // If hotseat doesn't fit with current width, increase column span to fit by multiple
+ // of 2.
+ while (hotseatBorderSpace < mMinHotseatIconSpacePx
+ && mHotseatColumnSpan < numWorkspaceColumns) {
+ updateHotseatWidthAndBorderSpace(mHotseatColumnSpan + 2);
+ }
+ }
+ if (isQsbInline) {
+ // If QSB is inline, reduce column span until it fits.
+ int maxHotseatWidthAllowedPx = getIconToIconWidthForColumns(numWorkspaceColumns);
+ int minHotseatWidthRequiredPx =
+ mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx;
+ while (minHotseatWidthRequiredPx > maxHotseatWidthAllowedPx
+ && mHotseatColumnSpan > 1) {
+ updateHotseatWidthAndBorderSpace(mHotseatColumnSpan - 1);
+ minHotseatWidthRequiredPx =
+ mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx;
+ }
+ }
hotseatQsbWidth = calculateQsbWidth(hotseatBorderSpace);
+
// Spaces should be correct when the nav buttons are not inline
if (!areNavButtonsInline) {
return;
@@ -928,6 +959,12 @@
} while (hotseatBorderSpace < mMinHotseatIconSpacePx && numShownHotseatIcons > 1);
}
+ private void updateHotseatWidthAndBorderSpace(int columns) {
+ mHotseatColumnSpan = columns;
+ mHotseatWidthPx = getIconToIconWidthForColumns(mHotseatColumnSpan);
+ hotseatBorderSpace = calculateHotseatBorderSpace(mHotseatWidthPx, /* numExtraBorder= */ 0);
+ }
+
private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
return getCellLayoutBorderSpace(idp, 1f);
}
@@ -951,6 +988,16 @@
return mInfo;
}
+ @VisibleForTesting
+ public int getHotseatColumnSpan() {
+ return mHotseatColumnSpan;
+ }
+
+ @VisibleForTesting
+ public int getHotseatWidthPx() {
+ return mHotseatWidthPx;
+ }
+
public Builder toBuilder(Context context) {
WindowBounds bounds = new WindowBounds(
widthPx, heightPx, availableWidthPx, availableHeightPx, rotationHint);
@@ -2113,7 +2160,8 @@
writer.println(prefix + pxToDpStr("allAppsLeftRightMargin", allAppsLeftRightMargin));
writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
- writer.println(prefix + "\tinv.hotseatColumnSpan: " + inv.hotseatColumnSpan[mTypeIndex]);
+ writer.println(prefix + "\tmHotseatColumnSpan: " + mHotseatColumnSpan);
+ writer.println(prefix + pxToDpStr("mHotseatWidthPx", mHotseatWidthPx));
writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
writer.println(prefix + pxToDpStr("hotseatBarBottomSpacePx", hotseatBarBottomSpacePx));
writer.println(prefix + pxToDpStr("mHotseatBarEdgePaddingPx",
@@ -2354,7 +2402,7 @@
mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds);
}
if (mIsGestureMode == null) {
- mIsGestureMode = mInfo.navigationMode.hasGestures;
+ mIsGestureMode = mInfo.getNavigationMode().hasGestures;
}
if (mDotRendererCache == null) {
mDotRendererCache = new SparseArray<>();
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index 51c7a05..eff748a 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -155,7 +155,7 @@
* Maps the touch (from 0..1) to the adapter position that should be visible.
* <p>Override in each subclass of this base class.
*/
- public abstract String scrollToPositionAtProgress(float touchFraction);
+ public abstract CharSequence scrollToPositionAtProgress(float touchFraction);
/**
* Updates the bounds for the scrollbar.
@@ -193,14 +193,4 @@
}
scrollToPosition(0);
}
-
- /**
- * Scrolls this recycler view to the bottom with easing and duration.
- */
- public void scrollToBottomWithMotion(int duration) {
- if (mScrollbar != null) {
- mScrollbar.reattachThumbToScroll();
- }
- smoothScrollBy(0, getAvailableScrollHeight(), Interpolators.EMPHASIZED, duration);
- }
}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 37737d8..8546454 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -82,6 +82,10 @@
return mHasVerticalHotseat ? (getCountY() - (rank + 1)) : 0;
}
+ boolean isHasVerticalHotseat() {
+ return mHasVerticalHotseat;
+ }
+
public void resetLayout(boolean hasVerticalHotseat) {
ActivityContext activityContext = ActivityContext.lookupContext(getContext());
boolean bubbleBarEnabled = activityContext.isBubbleBarEnabled();
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 42d4d50..54aea38 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
+import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
@@ -59,6 +60,7 @@
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Partner;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.WindowManagerProxy;
@@ -72,9 +74,10 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
-public class InvariantDeviceProfile {
+public class InvariantDeviceProfile implements SafeCloseable {
public static final String TAG = "IDP";
// We do not need any synchronization for this variable as its only written on UI thread.
@@ -162,7 +165,6 @@
*/
public int numDatabaseHotseatIcons;
- public int[] hotseatColumnSpan;
public float[] hotseatBarBottomSpace;
public float[] hotseatQsbSpace;
@@ -229,14 +231,14 @@
if (!newGridName.equals(gridName)) {
LauncherPrefs.get(context).put(GRID_NAME, newGridName);
}
- LockedUserState.get(context).runOnUserUnlocked(() -> {
- new DeviceGridState(this).writeToPrefs(context);
- });
+ LockedUserState.get(context).runOnUserUnlocked(() ->
+ new DeviceGridState(this).writeToPrefs(context));
DisplayController.INSTANCE.get(context).setPriorityListener(
(displayContext, info, flags) -> {
if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
- | CHANGE_NAVIGATION_MODE | CHANGE_TASKBAR_PINNING)) != 0) {
+ | CHANGE_NAVIGATION_MODE | CHANGE_TASKBAR_PINNING
+ | CHANGE_DESKTOP_MODE)) != 0) {
onConfigChanged(displayContext);
}
});
@@ -262,7 +264,7 @@
// Get the display info based on default display and interpolate it to existing display
Info defaultInfo = DisplayController.INSTANCE.get(context).getInfo();
- @DeviceType int defaultDeviceType = getDeviceType(defaultInfo);
+ @DeviceType int defaultDeviceType = defaultInfo.getDeviceType();
DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
defaultInfo,
getPredefinedDeviceProfiles(context, gridName, defaultDeviceType,
@@ -271,7 +273,7 @@
Context displayContext = context.createDisplayContext(display);
Info myInfo = new Info(displayContext);
- @DeviceType int deviceType = getDeviceType(myInfo);
+ @DeviceType int deviceType = myInfo.getDeviceType();
DisplayOption myDisplayOption = invDistWeightedInterpolate(
myInfo,
getPredefinedDeviceProfiles(context, gridName, deviceType,
@@ -295,6 +297,11 @@
initGrid(context, myInfo, result, deviceType);
}
+ @Override
+ public void close() {
+ DisplayController.INSTANCE.executeIfCreated(dc -> dc.setPriorityListener(null));
+ }
+
/**
* Reinitialize the current grid after a restore, where some grids might now be disabled.
*/
@@ -324,30 +331,13 @@
}
}
- private static @DeviceType int getDeviceType(Info displayInfo) {
- int flagPhone = 1 << 0;
- int flagTablet = 1 << 1;
-
- int type = displayInfo.supportedBounds.stream()
- .mapToInt(bounds -> displayInfo.isTablet(bounds) ? flagTablet : flagPhone)
- .reduce(0, (a, b) -> a | b);
- if (type == (flagPhone | flagTablet)) {
- // device has profiles supporting both phone and table modes
- return TYPE_MULTI_DISPLAY;
- } else if (type == flagTablet) {
- return TYPE_TABLET;
- } else {
- return TYPE_PHONE;
- }
- }
-
public static String getCurrentGridName(Context context) {
return LauncherPrefs.get(context).get(GRID_NAME);
}
private String initGrid(Context context, String gridName) {
Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
- @DeviceType int deviceType = getDeviceType(displayInfo);
+ @DeviceType int deviceType = displayInfo.getDeviceType();
ArrayList<DisplayOption> allOptions =
getPredefinedDeviceProfiles(context, gridName, deviceType,
@@ -358,6 +348,15 @@
return displayOption.grid.name;
}
+ /**
+ * @deprecated This is a temporary solution because on the backup and restore case we modify the
+ * IDP, this resets it. b/332974074
+ */
+ @Deprecated
+ public void reset(Context context) {
+ initGrid(context, getCurrentGridName(context));
+ }
+
@VisibleForTesting
public static String getDefaultGridName(Context context) {
return new InvariantDeviceProfile().initGrid(context, null);
@@ -419,7 +418,6 @@
numShownHotseatIcons = closestProfile.numHotseatIcons;
numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
- hotseatColumnSpan = closestProfile.hotseatColumnSpan;
hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace;
hotseatQsbSpace = displayOption.hotseatQsbSpace;
@@ -578,6 +576,45 @@
}
/**
+ * Returns the GridOption associated to the given file name or null if the fileName is not
+ * supported.
+ * Ej, launcher.db -> "normal grid", launcher_4_by_4.db -> "practical grid"
+ */
+ public GridOption getGridOptionFromFileName(Context context, String fileName) {
+ return parseAllGridOptions(context).stream()
+ .filter(gridOption -> Objects.equals(gridOption.dbFile, fileName))
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
+ * Returns the name of the given size on the current device or empty string if the size is not
+ * supported. Ej. 4x4 -> normal, 5x4 -> practical, etc.
+ * (Note: the name of the grid can be different for the same grid size depending of
+ * the values of the InvariantDeviceProfile)
+ *
+ */
+ public String getGridNameFromSize(Context context, Point size) {
+ return parseAllGridOptions(context).stream()
+ .filter(gridOption -> gridOption.numColumns == size.x
+ && gridOption.numRows == size.y)
+ .map(gridOption -> gridOption.name)
+ .findFirst()
+ .orElse("");
+ }
+
+ /**
+ * Returns the grid option for the given gridName on the current device (Note: the gridOption
+ * be different for the same gridName depending on the values of the InvariantDeviceProfile).
+ */
+ public GridOption getGridOptionFromName(Context context, String gridName) {
+ return parseAllGridOptions(context).stream()
+ .filter(gridOption -> Objects.equals(gridOption.name, gridName))
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
* @return all the grid options that can be shown on the device
*/
public List<GridOption> parseAllGridOptions(Context context) {
@@ -830,8 +867,6 @@
private final int numHotseatIcons;
private final int numDatabaseHotseatIcons;
- private final int[] hotseatColumnSpan = new int[COUNT_SIZES];
-
private final boolean[] inlineQsb = new boolean[COUNT_SIZES];
private @DimenRes int inlineNavButtonsEndSpacing;
@@ -882,17 +917,6 @@
numDatabaseHotseatIcons = a.getInt(
R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons);
- hotseatColumnSpan[INDEX_DEFAULT] = a.getInt(
- R.styleable.GridDisplayOption_hotseatColumnSpan, numColumns);
- hotseatColumnSpan[INDEX_LANDSCAPE] = a.getInt(
- R.styleable.GridDisplayOption_hotseatColumnSpanLandscape, numColumns);
- hotseatColumnSpan[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt(
- R.styleable.GridDisplayOption_hotseatColumnSpanTwoPanelLandscape,
- numColumns);
- hotseatColumnSpan[INDEX_TWO_PANEL_PORTRAIT] = a.getInt(
- R.styleable.GridDisplayOption_hotseatColumnSpanTwoPanelPortrait,
- numColumns);
-
inlineNavButtonsEndSpacing =
a.getResourceId(R.styleable.GridDisplayOption_inlineNavButtonsEndSpacing,
R.dimen.taskbar_button_margin_default);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c1ebbe5..4e566ab 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -28,6 +28,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.Flags.enableAddAppWidgetViaConfigActivityV2;
+import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
import static com.android.launcher3.Flags.enableWorkspaceInflation;
import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION;
@@ -37,6 +38,7 @@
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_PENDING_APPWIDGET;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_CREATE_APPWIDGET;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_CREATE_SHORTCUT;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_HOME_ROLE;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_PICK_APPWIDGET;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -66,7 +68,6 @@
import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE;
@@ -151,6 +152,7 @@
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.OvershootInterpolator;
+import android.widget.Toast;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
@@ -161,8 +163,10 @@
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
+import androidx.core.os.BuildCompat;
import androidx.window.embedding.RuleController;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
@@ -202,6 +206,8 @@
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.StringCache;
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;
@@ -220,7 +226,6 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.ActivityResultInfo;
import com.android.launcher3.util.ActivityTracker;
import com.android.launcher3.util.BackPressHandler;
@@ -233,6 +238,7 @@
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
@@ -259,6 +265,7 @@
import com.android.launcher3.widget.custom.CustomWidgetManager;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
+import com.android.launcher3.widget.util.WidgetSizes;
import com.android.systemui.plugins.LauncherOverlayPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.shared.LauncherOverlayManager;
@@ -305,7 +312,7 @@
private static boolean sIsNewProcess = true;
- private StateManager<LauncherState> mStateManager;
+ private StateManager<LauncherState, Launcher> mStateManager;
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
@@ -573,8 +580,8 @@
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
mOverlayManager = getDefaultOverlay();
- PluginManagerWrapper.INSTANCE.get(this).addPluginListener(this,
- LauncherOverlayPlugin.class, false /* allowedMultiple */);
+ PluginManagerWrapper.INSTANCE.get(this)
+ .addPluginListener(this, LauncherOverlayPlugin.class);
mRotationHelper.initialize();
TraceHelper.INSTANCE.endSection();
@@ -583,7 +590,8 @@
setTitle(R.string.home_screen);
mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
- if (com.android.launcher3.Flags.enableTwoPaneLauncherSettings()) {
+ if (BuildCompat.isAtLeastV()
+ && com.android.launcher3.Flags.enableTwoPaneLauncherSettings()) {
RuleController.getInstance(this).setRules(
RuleController.parseRules(this, R.xml.split_configuration));
}
@@ -660,6 +668,11 @@
// #5 state handler
return new OnBackAnimationCallback() {
@Override
+ public void onBackStarted(BackEvent backEvent) {
+ Launcher.this.onBackStarted();
+ }
+
+ @Override
public void onBackInvoked() {
onStateBack();
}
@@ -672,7 +685,7 @@
@Override
public void onBackCancelled() {
- mStateManager.getState().onBackCancelled(Launcher.this);
+ Launcher.this.onBackCancelled();
}
};
}
@@ -797,13 +810,19 @@
@Override
public void invalidateParent(ItemInfo info) {
if (info.container >= 0) {
- View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
- if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) {
+ View collectionIcon = getWorkspace().getHomescreenIconByItemId(info.container);
+ if (collectionIcon instanceof FolderIcon folderIcon
+ && collectionIcon.getTag() instanceof FolderInfo) {
if (new FolderGridOrganizer(getDeviceProfile())
.setFolderInfo((FolderInfo) folderIcon.getTag())
.isItemInPreview(info.rank)) {
folderIcon.invalidate();
}
+ } else if (collectionIcon instanceof AppPairIcon appPairIcon
+ && collectionIcon.getTag() instanceof AppPairInfo appPairInfo) {
+ if (appPairInfo.getContents().contains(info)) {
+ appPairIcon.getIconDrawableArea().redraw();
+ }
}
}
}
@@ -829,7 +848,7 @@
announceForAccessibility(R.string.item_added_to_workspace);
break;
case REQUEST_CREATE_APPWIDGET:
- completeAddAppWidget(appWidgetId, info, null, null, false, null);
+ completeAddAppWidget(appWidgetId, info, null, null, false, true, null);
break;
case REQUEST_RECONFIGURE_APPWIDGET:
getStatsLogManager().logger().withItemInfo(info).log(LAUNCHER_WIDGET_RECONFIGURED);
@@ -875,6 +894,17 @@
}
mPendingActivityResult = null;
+ if (requestCode == REQUEST_HOME_ROLE) {
+ if (resultCode != RESULT_OK) {
+ Toast.makeText(
+ this,
+ this.getString(R.string.set_default_home_app,
+ this.getString(R.string.derived_app_name)),
+ Toast.LENGTH_LONG).show();
+ }
+ return;
+ }
+
// Reset the startActivity waiting flag
final PendingRequestArgs requestArgs = mPendingRequestArgs;
setWaitingForResult(null);
@@ -1027,7 +1057,7 @@
requestArgs.getWidgetHandler().getProviderInfo(this));
boundWidget = layout;
onCompleteRunnable = () -> {
- completeAddAppWidget(appWidgetId, requestArgs, layout, null, false, null);
+ completeAddAppWidget(appWidgetId, requestArgs, layout, null, false, true, null);
if (!isInState(EDIT_MODE)) {
mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
}
@@ -1247,6 +1277,9 @@
getAllAppsExitEvent().ifPresent(getStatsLogManager().logger()::log);
mAllAppsSessionLogId = null;
}
+
+ // Set screen title for Talkback
+ setTitle(state.getTitle());
}
/**
@@ -1347,7 +1380,7 @@
// Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
// default state, otherwise we will update to the wrong offsets in RTL
mWorkspace.lockWallpaperToDefaultPage();
- if (!ENABLE_SMARTSPACE_REMOVAL.get()) {
+ if (!enableSmartspaceRemovalToggle()) {
mWorkspace.bindAndInitFirstWorkspaceScreen();
}
mDragController.addDragListener(mWorkspace);
@@ -1458,7 +1491,8 @@
@Thunk
void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo,
@Nullable AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo,
- boolean showPendingWidget, @Nullable Bitmap widgetPreviewBitmap) {
+ boolean showPendingWidget, boolean updateWidgetSize,
+ @Nullable Bitmap widgetPreviewBitmap) {
if (appWidgetInfo == null) {
appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId,
@@ -1483,11 +1517,10 @@
if (showPendingWidget) {
launcherInfo.restoreStatus = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
PendingAppWidgetHostView pendingAppWidgetHostView = new PendingAppWidgetHostView(
- this, mAppWidgetHolder, launcherInfo, appWidgetInfo);
- pendingAppWidgetHostView.setPreviewBitmap(widgetPreviewBitmap);
+ this, mAppWidgetHolder, launcherInfo, appWidgetInfo, widgetPreviewBitmap);
hostView = pendingAppWidgetHostView;
} else if (hostView instanceof PendingAppWidgetHostView) {
- ((PendingAppWidgetHostView) hostView).setPreviewBitmap(null);
+ ((PendingAppWidgetHostView) hostView).setPreviewBitmapAndUpdateBackground(null);
// User has selected a widget config and exited the config activity, we can trigger
// re-inflation of PendingAppWidgetHostView to replace it with
// LauncherAppWidgetHostView in workspace.
@@ -1500,8 +1533,14 @@
reInflatedHostView,
(LauncherAppWidgetInfo) reInflatedHostView.getTag(),
presenterPos);
+ // We always update widget size after re-inflating PendingAppWidgetHostView
+ WidgetSizes.updateWidgetSizeRanges(
+ reInflatedHostView, this, itemInfo.spanX, itemInfo.spanY);
return;
}
+ if (updateWidgetSize) {
+ WidgetSizes.updateWidgetSizeRanges(hostView, this, itemInfo.spanX, itemInfo.spanY);
+ }
if (itemInfo instanceof PendingAddWidgetInfo) {
launcherInfo.sourceContainer = ((PendingAddWidgetInfo) itemInfo).sourceContainer;
} else if (itemInfo instanceof PendingRequestArgs) {
@@ -1531,7 +1570,13 @@
LauncherAppWidgetInfo launcherInfo,
CellPos presenterPos) {
CellLayout cellLayout = getCellLayout(launcherInfo.container, presenterPos.screenId);
- if (mStateManager.getState() == NORMAL) {
+ // We should wait until launcher is not animating to show resize frame so that
+ // {@link View#hasIdentityMatrix()} returns true (no scale effect) from CellLayout and
+ // Workspace (they are widget's parent view). Otherwise widget's
+ // {@link View#getLocationInWindow(int[])} will set skewed location, causing resize
+ // frame not showing at skewed location in
+ // {@link AppWidgetResizeFrame#snapToWidget(boolean)}.
+ if (mStateManager.getState() == NORMAL && !mStateManager.isInTransition()) {
AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
} else {
mStateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
@@ -1627,9 +1672,10 @@
} else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
showAllAppsFromIntent(alreadyOnHome);
} else if (INTENT_ACTION_ALL_APPS_TOGGLE.equals(intent.getAction())) {
- toggleAllAppsFromIntent(alreadyOnHome);
+ toggleAllAppsSearch(alreadyOnHome);
} else if (Intent.ACTION_SHOW_WORK_APPS.equals(intent.getAction())) {
- showAllAppsWorkTabFromIntent(alreadyOnHome);
+ showAllAppsWithSelectedTabFromIntent(alreadyOnHome,
+ ActivityAllAppsContainerView.AdapterHolder.WORK);
}
TraceHelper.INSTANCE.endSection();
@@ -1640,7 +1686,12 @@
// Overridden
}
- protected void toggleAllAppsFromIntent(boolean alreadyOnHome) {
+ /** Toggles Launcher All Apps with keyboard ready for search. */
+ public void toggleAllAppsSearch() {
+ toggleAllAppsSearch(/* alreadyOnHome= */ true);
+ }
+
+ protected void toggleAllAppsSearch(boolean alreadyOnHome) {
if (getStateManager().isInStableState(ALL_APPS)) {
getStateManager().goToState(NORMAL, alreadyOnHome);
} else {
@@ -1661,13 +1712,19 @@
}
protected void showAllAppsFromIntent(boolean alreadyOnHome) {
- AbstractFloatingView.closeAllOpenViews(this);
- getStateManager().goToState(ALL_APPS, alreadyOnHome);
+ showAllAppsWithSelectedTabFromIntent(alreadyOnHome,
+ ActivityAllAppsContainerView.AdapterHolder.MAIN);
}
- private void showAllAppsWorkTabFromIntent(boolean alreadyOnHome) {
- showAllAppsFromIntent(alreadyOnHome);
- mAppsView.switchToTab(ActivityAllAppsContainerView.AdapterHolder.WORK);
+ private void showAllAppsWithSelectedTabFromIntent(boolean alreadyOnHome, int tab) {
+ AbstractFloatingView.closeAllOpenViews(this);
+ getStateManager().goToState(ALL_APPS, alreadyOnHome);
+ if (mAppsView.isSearching()) {
+ mAppsView.getSearchUiManager().resetSearch();
+ }
+ if (mAppsView.getCurrentPage() != tab) {
+ mAppsView.switchToTab(tab);
+ }
}
/**
@@ -1822,7 +1879,9 @@
if (isActivityStarted) {
DragView dropView = getDragLayer().clearAnimatedView();
if (dropView != null && dropView.containsAppWidgetHostView()) {
- widgetPreviewBitmap = getBitmapFromView(dropView.getContentView());
+ // Extracting Bitmap from dropView instead of its content view produces the correct
+ // bitmap.
+ widgetPreviewBitmap = getBitmapFromView(dropView);
}
}
@@ -1831,7 +1890,7 @@
: () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
completeAddAppWidget(appWidgetId, info, boundWidget,
addFlowHandler.getProviderInfo(this), addFlowHandler.needsConfigure(),
- widgetPreviewBitmap);
+ false, widgetPreviewBitmap);
mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
}
@@ -1987,24 +2046,26 @@
public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb,
@Nullable final String reason) {
if (itemInfo instanceof WorkspaceItemInfo) {
- // Remove the shortcut from the folder before removing it from launcher
- View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
- if (folderIcon instanceof FolderIcon) {
- ((FolderInfo) folderIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true);
+ View collectionIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
+ if (collectionIcon instanceof FolderIcon) {
+ // Remove the shortcut from the folder before removing it from launcher
+ ((FolderInfo) collectionIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true);
+ } else if (collectionIcon instanceof AppPairIcon appPairIcon) {
+ removeItem(appPairIcon, appPairIcon.getInfo(), deleteFromDb,
+ "removing app pair because one of its member apps was removed");
} else {
mWorkspace.removeWorkspaceItem(v);
}
if (deleteFromDb) {
getModelWriter().deleteItemFromDatabase(itemInfo, reason);
}
- } else if (itemInfo instanceof FolderInfo) {
- final FolderInfo folderInfo = (FolderInfo) itemInfo;
+ } else if (itemInfo instanceof CollectionInfo ci) {
if (v instanceof FolderIcon) {
((FolderIcon) v).removeListeners();
}
mWorkspace.removeWorkspaceItem(v);
if (deleteFromDb) {
- getModelWriter().deleteFolderAndContentsFromDatabase(folderInfo);
+ getModelWriter().deleteCollectionAndContentsFromDatabase(ci);
}
} else if (itemInfo instanceof LauncherAppWidgetInfo) {
final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
@@ -2047,8 +2108,16 @@
getOnBackAnimationCallback().onBackInvoked();
}
+ protected void onBackStarted() {
+ mStateManager.getState().onBackStarted(this);
+ }
+
protected void onStateBack() {
- mStateManager.getState().onBackPressed(this);
+ mStateManager.getState().onBackInvoked(this);
+ }
+
+ protected void onBackCancelled() {
+ mStateManager.getState().onBackCancelled(this);
}
protected void onScreenOnChanged(boolean isOn) {
@@ -2357,7 +2426,8 @@
* Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
* animation.
*
- * @param preferredItemId The id of the preferred item to match to if it exists.
+ * @param preferredItemId The id of the preferred item to match to if it exists,
+ * or ItemInfo#NO_MATCHING_ID if you want to not match by item id
* @param packageName The package name of the app to match.
* @param user The user of the app to match.
* @param supportsAllAppsState If true and we are in All Apps state, looks for view in All Apps.
@@ -2457,7 +2527,13 @@
final int itemCount = container.getChildCount();
for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
View item = container.getChildAt(itemIdx);
- if (op.test((ItemInfo) item.getTag())) {
+ if (item instanceof ViewGroup viewGroup) {
+ View view = mapOverViewGroup(viewGroup, op);
+ if (view != null) {
+ return view;
+ }
+ }
+ if (item.getTag() instanceof ItemInfo itemInfo && op.test(itemInfo)) {
return item;
}
}
@@ -2634,6 +2710,7 @@
mModel.dumpState(prefix, fd, writer, args);
mOverlayManager.dump(prefix, writer);
+ ACTIVITY_TRACKER.dump(prefix, writer);
}
/**
@@ -2698,7 +2775,7 @@
}
@Override
- protected void collectStateHandlers(List<StateHandler> out) {
+ public void collectStateHandlers(List<StateHandler<LauncherState>> out) {
out.add(getAllAppsController());
out.add(getWorkspace());
}
@@ -2720,8 +2797,9 @@
}
private void updateDisallowBack() {
- if (Flags.enableDesktopWindowingMode()) {
- // Do not disable back in launcher when prototype behavior is enabled
+ if (BuildCompat.isAtLeastV() && Flags.enableDesktopWindowingMode()
+ && mDeviceProfile.isTablet) {
+ // TODO(b/330183377) disable back in launcher when when we productionize
return;
}
LauncherRootView rv = getRootView();
@@ -2841,8 +2919,8 @@
* Returns {@code true} if there are visible tasks with windowing mode set to
* {@link android.app.WindowConfiguration#WINDOWING_MODE_FREEFORM}
*/
- public boolean areFreeformTasksVisible() {
- return false; // Base launcher does not track freeform tasks
+ public boolean areDesktopTasksVisible() {
+ return false; // Base launcher does not track desktop tasks
}
// Getters and Setters
@@ -2918,7 +2996,7 @@
}
@Override
- public StateManager<LauncherState> getStateManager() {
+ public StateManager<LauncherState, Launcher> getStateManager() {
return mStateManager;
}
@@ -3077,4 +3155,4 @@
}
// End of Getters and Setters
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index c20f323..6a9d170 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -220,17 +220,28 @@
/**
* Utility method to create an {@link AnimatorListener} which executes a callback on animation
- * cancel.
+ * cancel. Once the cancel has been dispatched, this listener will no longer be called.
*/
- public static AnimatorListener newCancelListener(Runnable callback) {
- return new AnimatorListenerAdapter() {
+ public static AnimatorListener newSingleUseCancelListener(Runnable callback) {
+ return newCancelListener(callback, true);
+ }
+ /**
+ * Utility method to create an {@link AnimatorListener} which executes a callback on animation
+ * cancel.
+ *
+ * @param isSingleUse {@code true} means the callback will be called at most once
+ */
+ public static AnimatorListener newCancelListener(Runnable callback, boolean isSingleUse) {
+ return new AnimatorListenerAdapter() {
boolean mDispatched = false;
@Override
public void onAnimationCancel(Animator animation) {
if (!mDispatched) {
- mDispatched = true;
+ if (isSingleUse) {
+ mDispatched = true;
+ }
callback.run();
}
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 60a6be6..3b8ff62 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -19,9 +19,9 @@
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.content.Context.RECEIVER_EXPORTED;
+import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
import static com.android.launcher3.LauncherPrefs.ICON_STATE;
import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
@@ -39,6 +39,7 @@
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.core.os.BuildCompat;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.IconCache;
@@ -52,6 +53,7 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SafeCloseable;
@@ -78,14 +80,10 @@
private final RunnableList mOnTerminateCallback = new RunnableList();
- public static LauncherAppState getInstance(final Context context) {
+ public static LauncherAppState getInstance(Context context) {
return INSTANCE.get(context);
}
- public static LauncherAppState getInstanceNoCreate() {
- return INSTANCE.getNoCreate();
- }
-
public Context getContext() {
return mContext;
}
@@ -110,7 +108,7 @@
mOnTerminateCallback.add(() ->
mContext.getSystemService(LauncherApps.class).unregisterCallback(callbacks));
- if (Utilities.enableSupportForArchiving()) {
+ if (BuildCompat.isAtLeastV() && Flags.enableSupportForArchiving()) {
ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
params.setEnableUnarchivalConfirmation(false);
launcherApps.setArchiveCompatibility(params);
@@ -130,7 +128,7 @@
.addUserEventListener(mModel::onUserEvent);
mOnTerminateCallback.add(userChangeListener::close);
- if (ENABLE_SMARTSPACE_REMOVAL.get()) {
+ if (enableSmartspaceRemovalToggle()) {
OnSharedPreferenceChangeListener firstPagePinnedItemListener =
new OnSharedPreferenceChangeListener() {
@Override
@@ -185,7 +183,7 @@
mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
iconCacheFileName, mIconProvider);
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
- iconCacheFileName != null);
+ PackageManagerHelper.INSTANCE.get(context), iconCacheFileName != null);
mOnTerminateCallback.add(mIconCache::close);
mOnTerminateCallback.add(mModel::destroy);
}
@@ -210,7 +208,7 @@
}
private void refreshAndReloadLauncher() {
- LauncherIcons.clearPool();
+ LauncherIcons.clearPool(mContext);
mIconCache.updateIconParams(
mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
mModel.forceReload();
@@ -261,7 +259,7 @@
@Override
public void onSystemIconStateChanged(String iconState) {
- IconShape.init(mContext);
+ IconShape.INSTANCE.get(mContext).pickBestShape(mContext);
refreshAndReloadLauncher();
LauncherPrefs.get(mContext).put(ICON_STATE, iconState);
}
diff --git a/src/com/android/launcher3/LauncherConstants.java b/src/com/android/launcher3/LauncherConstants.java
index 1abfeb9..445fb41 100644
--- a/src/com/android/launcher3/LauncherConstants.java
+++ b/src/com/android/launcher3/LauncherConstants.java
@@ -41,6 +41,7 @@
public static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
public static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
+ public static final int REQUEST_HOME_ROLE = 14;
static final int REQUEST_CREATE_SHORTCUT = 1;
static final int REQUEST_CREATE_APPWIDGET = 5;
static final int REQUEST_PICK_APPWIDGET = 9;
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 99fca62..ca1b2a9 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -19,7 +19,9 @@
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
+import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
+import static com.android.launcher3.icons.cache.BaseIconCache.EMPTY_CLASS_NAME;
import static com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE;
import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE;
import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE;
@@ -27,6 +29,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
@@ -45,16 +48,16 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.AddWorkspaceItemsTask;
import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BaseLauncherBinder;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.CacheDataUpdatedTask;
import com.android.launcher3.model.ItemInstallQueue;
-import com.android.launcher3.model.LauncherBinder;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.model.ModelDelegate;
import com.android.launcher3.model.ModelLauncherCallbacks;
+import com.android.launcher3.model.ModelTaskController;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.PackageInstallStateChangedTask;
import com.android.launcher3.model.PackageUpdatedTask;
@@ -70,6 +73,7 @@
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
@@ -79,7 +83,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CancellationException;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -96,6 +99,8 @@
@NonNull
private final LauncherAppState mApp;
@NonNull
+ private final PackageManagerHelper mPmHelper;
+ @NonNull
private final ModelDbController mModelDbController;
@NonNull
private final Object mLock = new Object();
@@ -150,12 +155,13 @@
LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
@NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
- final boolean isPrimaryInstance) {
+ @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance) {
mApp = app;
+ mPmHelper = pmHelper;
mModelDbController = new ModelDbController(context);
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
- mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel,
- isPrimaryInstance);
+ mModelDelegate = ModelDelegate.newInstance(context, app, mPmHelper, mBgAllAppsList,
+ mBgDataModel, isPrimaryInstance);
}
@NonNull
@@ -271,6 +277,9 @@
enqueueModelUpdateTask(new PackageUpdatedTask(
PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
}
+ if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+ LauncherPrefs.get(mApp.getContext()).put(WORK_EDU_STEP, 0);
+ }
}
/**
@@ -362,7 +371,7 @@
MAIN_EXECUTOR.execute(cb::clearPendingBinds);
}
- LauncherBinder launcherBinder = new LauncherBinder(
+ BaseLauncherBinder launcherBinder = new BaseLauncherBinder(
mApp, mBgDataModel, mBgAllAppsList, callbacksList);
if (bindDirectly) {
// Divide the set of loaded items into those that we are binding synchronously,
@@ -423,13 +432,9 @@
@Override
public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) {
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- apps.addPromiseApp(app.getContext(), sessionInfo);
- bindApplicationsIfNeeded();
- }
+ enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ apps.addPromiseApp(mApp.getContext(), sessionInfo);
+ taskController.bindApplicationsIfNeeded();
});
}
}
@@ -437,50 +442,55 @@
@Override
public void onSessionFailure(@NonNull final String packageName,
@NonNull final UserHandle user) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- IconCache iconCache = app.getIconCache();
- final IntSet removedIds = new IntSet();
- HashSet<WorkspaceItemInfo> archivedItemsToCacheRefresh = new HashSet<>();
- HashSet<String> archivedPackagesToCacheRefresh = new HashSet<>();
- synchronized (dataModel) {
- for (ItemInfo info : dataModel.itemsIdMap) {
- if (info instanceof WorkspaceItemInfo
- && ((WorkspaceItemInfo) info).hasPromiseIconUi()
- && user.equals(info.user)
- && info.getIntent() != null) {
- if (TextUtils.equals(packageName, info.getIntent().getPackage())) {
- removedIds.add(info.id);
- }
- if (((WorkspaceItemInfo) info).isArchived()) {
- WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info;
- // Remove package cache icon for archived app in case of a session
- // failure.
- mApp.getIconCache().removeIconsForPkg(packageName, user);
- // Refresh icons on the workspace for archived apps.
- iconCache.getTitleAndIcon(workspaceItem,
- workspaceItem.usingLowResIcon());
- archivedPackagesToCacheRefresh.add(packageName);
- archivedItemsToCacheRefresh.add(workspaceItem);
- }
+ enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ IconCache iconCache = mApp.getIconCache();
+ final IntSet removedIds = new IntSet();
+ HashSet<WorkspaceItemInfo> archivedWorkspaceItemsToCacheRefresh = new HashSet<>();
+ boolean isAppArchived = PackageManagerHelper.INSTANCE.get(mApp.getContext())
+ .isAppArchivedForUser(packageName, user);
+ synchronized (dataModel) {
+ if (isAppArchived) {
+ // Remove package icon cache entry for archived app in case of a session
+ // failure.
+ mApp.getIconCache().remove(
+ new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
+ user);
+ }
+
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info instanceof WorkspaceItemInfo
+ && ((WorkspaceItemInfo) info).hasPromiseIconUi()
+ && user.equals(info.user)
+ && info.getIntent() != null) {
+ if (TextUtils.equals(packageName, info.getIntent().getPackage())) {
+ removedIds.add(info.id);
}
- }
- if (!archivedPackagesToCacheRefresh.isEmpty()) {
- apps.updateIconsAndLabels(archivedPackagesToCacheRefresh, user);
+ if (((WorkspaceItemInfo) info).isArchived()) {
+ WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info;
+ // Refresh icons on the workspace for archived apps.
+ iconCache.getTitleAndIcon(workspaceItem,
+ workspaceItem.usingLowResIcon());
+ archivedWorkspaceItemsToCacheRefresh.add(workspaceItem);
+ }
}
}
- if (!removedIds.isEmpty()) {
- deleteAndBindComponentsRemoved(
- ItemInfoMatcher.ofItemIds(removedIds),
- "removed because install session failed");
+ if (isAppArchived) {
+ apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user);
}
- if (!archivedItemsToCacheRefresh.isEmpty()) {
- bindUpdatedWorkspaceItems(archivedItemsToCacheRefresh.stream().toList());
- bindApplicationsIfNeeded();
- }
+ }
+
+ if (!removedIds.isEmpty() && !isAppArchived) {
+ taskController.deleteAndBindComponentsRemoved(
+ ItemInfoMatcher.ofItemIds(removedIds),
+ "removed because install session failed");
+ }
+ if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) {
+ taskController.bindUpdatedWorkspaceItems(
+ archivedWorkspaceItemsToCacheRefresh.stream().toList());
+ }
+ if (isAppArchived) {
+ taskController.bindApplicationsIfNeeded();
}
});
}
@@ -571,13 +581,9 @@
*/
public void onWidgetLabelsUpdated(@NonNull final HashSet<String> updatedPackages,
@NonNull final UserHandle user) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, app);
- bindUpdatedWidgets(dataModel);
- }
+ enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp);
+ taskController.bindUpdatedWidgets(dataModel);
});
}
@@ -585,8 +591,15 @@
if (mModelDestroyed) {
return;
}
- task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
- MODEL_EXECUTOR.execute(task);
+ MODEL_EXECUTOR.execute(() -> {
+ if (!isModelLoaded()) {
+ // Loader has not yet run.
+ return;
+ }
+ ModelTaskController controller = new ModelTaskController(
+ mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR);
+ task.execute(controller, mBgDataModel, mBgAllAppsList);
+ });
}
/**
@@ -598,18 +611,10 @@
void execute(@NonNull Callbacks callbacks);
}
- /**
- * A runnable which changes/updates the data model of the launcher based on certain events.
- */
- public interface ModelUpdateTask extends Runnable {
+ public interface ModelUpdateTask {
- /**
- * Called before the task is posted to initialize the internal state.
- */
- void init(@NonNull LauncherAppState app, @NonNull LauncherModel model,
- @NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList,
- @NonNull Executor uiExecutor);
-
+ void execute(@NonNull ModelTaskController taskController,
+ @NonNull BgDataModel dataModel, @NonNull AllAppsList apps);
}
public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si,
@@ -626,27 +631,19 @@
*/
public void updateAndBindWorkspaceItem(
@NonNull final Supplier<WorkspaceItemInfo> itemProvider) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- WorkspaceItemInfo info = itemProvider.get();
- getModelWriter().updateItemInDatabase(info);
- ArrayList<WorkspaceItemInfo> update = new ArrayList<>();
- update.add(info);
- bindUpdatedWorkspaceItems(update);
- }
+ enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ WorkspaceItemInfo info = itemProvider.get();
+ taskController.getModelWriter().updateItemInDatabase(info);
+ ArrayList<WorkspaceItemInfo> update = new ArrayList<>();
+ update.add(info);
+ taskController.bindUpdatedWorkspaceItems(update);
});
}
public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- dataModel.widgetsModel.update(app, packageUser);
- bindUpdatedWidgets(dataModel);
- }
+ enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ dataModel.widgetsModel.update(taskController.getApp(), packageUser);
+ taskController.bindUpdatedWidgets(dataModel);
});
}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index b0a644b..13181e8 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -19,8 +19,6 @@
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
-import android.util.Log
-import android.view.ViewConfiguration
import androidx.annotation.VisibleForTesting
import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
@@ -32,16 +30,15 @@
import com.android.launcher3.states.RotationHelper
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.SafeCloseable
import com.android.launcher3.util.Themes
/**
* Use same context for shared preferences, so that we use a single cached instance
*
* TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
- * TODO(b/274501660): Fix ReorderWidgets#simpleReorder test before enabling
- * isBootAwareStartupDataEnabled
*/
-class LauncherPrefs(private val encryptedContext: Context) {
+class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
private val deviceProtectedStorageContext =
encryptedContext.createDeviceProtectedStorageContext()
@@ -52,22 +49,8 @@
private val Item.encryptedPrefs
get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
- // This call to `SharedPreferences` needs to be explicit rather than using `get` since doing so
- // would result in a circular dependency between `isStartupDataMigrated` and `choosePreferences`
- val isStartupDataMigrated: Boolean
- get() =
- bootAwarePrefs.getBoolean(
- IS_STARTUP_DATA_MIGRATED.sharedPrefKey,
- IS_STARTUP_DATA_MIGRATED.defaultValue
- )
-
private fun chooseSharedPreferences(item: Item): SharedPreferences =
- if (
- (moveStartupDataToDeviceProtectedStorageIsEnabled &&
- item.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
- isStartupDataMigrated) || item.encryptionType == EncryptionType.DEVICE_PROTECTED
- )
- bootAwarePrefs
+ if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs
else item.encryptedPrefs
/** Wrapper around `getInner` for a `ContextualItem` */
@@ -147,11 +130,7 @@
.toMutableMap()
val bootAwareUpdates =
- updates.filter {
- (it.first.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
- moveStartupDataToDeviceProtectedStorageIsEnabled) ||
- it.first.encryptionType == EncryptionType.DEVICE_PROTECTED
- }
+ updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED }
if (bootAwareUpdates.isNotEmpty()) {
updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
}
@@ -252,12 +231,7 @@
.groupBy { it.encryptedPrefs }
.toMutableMap()
- val bootAwareUpdates =
- items.filter {
- (it.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
- moveStartupDataToDeviceProtectedStorageIsEnabled) ||
- it.encryptionType == EncryptionType.DEVICE_PROTECTED
- }
+ val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED }
if (bootAwareUpdates.isNotEmpty()) {
itemsPerFile[bootAwarePrefs] = bootAwareUpdates
}
@@ -269,24 +243,9 @@
}
}
- fun migrateStartupDataToDeviceProtectedStorage() {
- if (!moveStartupDataToDeviceProtectedStorageIsEnabled) return
-
- Log.d(
- TAG,
- "Migrating data to unencrypted shared preferences to enable preloading " +
- "while the user is locked the next time the device reboots."
- )
-
- with(bootAwarePrefs.edit()) {
- ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.forEach { putValue(it, get(it)) }
- putBoolean(IS_STARTUP_DATA_MIGRATED.sharedPrefKey, true)
- apply()
- }
- }
+ override fun close() {}
companion object {
- private const val TAG = "LauncherPrefs"
@VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
@JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
@@ -294,96 +253,39 @@
@JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
+ const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
@JvmField
- val ICON_STATE =
- nonRestorableItem("pref_icon_shape_path", "", EncryptionType.MOVE_TO_DEVICE_PROTECTED)
+ val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
+
@JvmField
- val ALL_APPS_OVERVIEW_THRESHOLD =
- nonRestorableItem(
- "pref_all_apps_overview_threshold",
- 180,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
+ val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
@JvmField
- val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
- nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
- @JvmField
- val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
- nonRestorableItem(
- "LPNH_TIMEOUT_MS",
- ViewConfiguration.getLongPressTimeout(),
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
- @JvmField
- val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT =
- nonRestorableItem(
- "LPNH_HAPTIC_HINT_START_SCALE_PERCENT",
- 0,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
- @JvmField
- val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT =
- nonRestorableItem(
- "LPNH_HAPTIC_HINT_END_SCALE_PERCENT",
- 100,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
- @JvmField
- val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT =
- nonRestorableItem(
- "LPNH_HAPTIC_HINT_SCALE_EXPONENT",
- 1,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
- @JvmField
- val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS =
- nonRestorableItem(
- "LPNH_HAPTIC_HINT_ITERATIONS",
- 50,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
- @JvmField
- val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY =
- nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
- @JvmField
- val PRIVATE_SPACE_APPS =
- nonRestorableItem("pref_private_space_apps", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
- @JvmField val ENABLE_TWOLINE_ALLAPPS_TOGGLE =
- backedUpItem("pref_enable_two_line_toggle", false)
- @JvmField
- val THEMED_ICONS =
- backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
+ val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
@JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
@JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
@JvmField
val WORKSPACE_SIZE =
- backedUpItem(
- DeviceGridState.KEY_WORKSPACE_SIZE,
- "",
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
+ backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
@JvmField
val HOTSEAT_COUNT =
- backedUpItem(
- DeviceGridState.KEY_HOTSEAT_COUNT,
- -1,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
+ backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
@JvmField
val TASKBAR_PINNING =
backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
+ @JvmField
+ val TASKBAR_PINNING_IN_DESKTOP_MODE =
+ backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
@JvmField
val DEVICE_TYPE =
backedUpItem(
DeviceGridState.KEY_DEVICE_TYPE,
InvariantDeviceProfile.TYPE_PHONE,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ EncryptionType.ENCRYPTED
)
@JvmField
- val DB_FILE =
- backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.MOVE_TO_DEVICE_PROTECTED)
+ val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
@JvmField
val SHOULD_SHOW_SMARTSPACE =
backedUpItem(
@@ -396,15 +298,11 @@
backedUpItem(
RestoreDbTask.RESTORED_DEVICE_TYPE,
InvariantDeviceProfile.TYPE_PHONE,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ EncryptionType.ENCRYPTED
)
@JvmField
val IS_FIRST_LOAD_AFTER_RESTORE =
- nonRestorableItem(
- FIRST_LOAD_AFTER_RESTORE_KEY,
- false,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
+ nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
@JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
@JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
@JvmField
@@ -413,7 +311,7 @@
"idp_grid_name",
isBackedUp = true,
defaultValue = null,
- encryptionType = EncryptionType.MOVE_TO_DEVICE_PROTECTED,
+ encryptionType = EncryptionType.ENCRYPTED,
type = String::class.java
)
@JvmField
@@ -421,24 +319,11 @@
backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
}
- @JvmField
- val IS_STARTUP_DATA_MIGRATED =
- ConstantItem(
- "is_startup_data_boot_aware",
- isBackedUp = false,
- defaultValue = false,
- encryptionType = EncryptionType.DEVICE_PROTECTED
- )
// Preferences for widget configurations
@JvmField
val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
- @JvmField
- val WIDGETS_EDUCATION_DIALOG_SEEN =
- backedUpItem("launcher.widgets_education_dialog_seen", false)
- @JvmField
- val WIDGETS_EDUCATION_TIP_SEEN = backedUpItem("launcher.widgets_education_tip_seen", false)
@JvmStatic
fun <T> backedUpItem(
@@ -493,12 +378,6 @@
}
}
-// It is a var because the unit tests are setting this to true so they can run.
-var moveStartupDataToDeviceProtectedStorageIsEnabled: Boolean =
- com.android.launcher3.config.FeatureFlags.MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE.get()
-
-private val ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE: MutableSet<ConstantItem<*>> = mutableSetOf()
-
abstract class Item {
abstract val sharedPrefKey: String
abstract val isBackedUp: Boolean
@@ -518,14 +397,6 @@
// The default value can be null. If so, the type needs to be explicitly stated, or else NPE
override val type: Class<out T> = defaultValue!!::class.java
) : Item() {
- init {
- if (
- encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
- moveStartupDataToDeviceProtectedStorageIsEnabled
- ) {
- ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.add(this)
- }
- }
fun get(c: Context): T = LauncherPrefs.get(c).get(this)
}
@@ -552,5 +423,4 @@
enum class EncryptionType {
ENCRYPTED,
DEVICE_PROTECTED,
- MOVE_TO_DEVICE_PROTECTED
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 4e0ba62..6e2d357 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -48,11 +48,11 @@
*/
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
- if (appState == null || !appState.getModel().isModelLoaded()) {
- return;
- }
- appState.getModel().dumpState("", fd, writer, args);
+ LauncherAppState.INSTANCE.executeIfCreated(appState -> {
+ if (appState.getModel().isModelLoaded()) {
+ appState.getModel().dumpState("", fd, writer, args);
+ }
+ });
}
@Override
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 34ebaf2..87ac193 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -139,6 +139,11 @@
public static final int ITEM_TYPE_SEARCH_ACTION = 9;
/**
+ * Private space install app button.
+ */
+ public static final int ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON = 11;
+
+ /**
* The custom icon bitmap.
* <P>Type: BLOB</P>
*/
@@ -178,6 +183,7 @@
public static final int CONTAINER_SHORTCUTS = -107;
public static final int CONTAINER_SETTINGS = -108;
public static final int CONTAINER_TASKSWITCHER = -109;
+ public static final int CONTAINER_PRIVATESPACE = -110;
// Represents any of the extended containers implemented in non-AOSP variants.
public static final int EXTENDED_CONTAINERS = -200;
@@ -206,6 +212,8 @@
case ITEM_TYPE_TASK: return "TASK";
case ITEM_TYPE_QSB: return "QSB";
case ITEM_TYPE_APP_PAIR: return "APP_PAIR";
+ case ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON:
+ return "PRIVATE_SPACE_INSTALL_APP_BUTTON";
default: return String.valueOf(type);
}
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 6e66c14..102189b 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -38,6 +38,7 @@
import android.view.animation.Interpolator;
import androidx.annotation.FloatRange;
+import androidx.annotation.StringRes;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StateManager;
@@ -83,7 +84,7 @@
public static final int FLAG_HAS_SYS_UI_SCRIM = BaseState.getFlag(4);
// Flag to inticate that all popups should be closed when this state is enabled.
public static final int FLAG_CLOSE_POPUPS = BaseState.getFlag(5);
- public static final int FLAG_OVERVIEW_UI = BaseState.getFlag(6);
+ public static final int FLAG_RECENTS_VIEW_VISIBLE = BaseState.getFlag(6);
// Flag indicating that hotseat and its contents are not accessible.
public static final int FLAG_HOTSEAT_INACCESSIBLE = BaseState.getFlag(7);
@@ -158,14 +159,14 @@
/**
* True if the state has overview panel visible.
*/
- public final boolean overviewUi;
+ public final boolean isRecentsViewVisible;
private final int mFlags;
public LauncherState(int id, int statsLogOrdinal, int flags) {
this.statsLogOrdinal = statsLogOrdinal;
this.mFlags = flags;
- this.overviewUi = (flags & FLAG_OVERVIEW_UI) != 0;
+ this.isRecentsViewVisible = (flags & FLAG_RECENTS_VIEW_VISIBLE) != 0;
this.ordinal = id;
sAllStates[id] = this;
}
@@ -369,6 +370,10 @@
return launcher.getWorkspace().getCurrentPageDescription();
}
+ public @StringRes int getTitle() {
+ return R.string.home_screen;
+ }
+
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
if ((this != NORMAL && this != HINT_STATE)
|| !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) {
@@ -424,20 +429,29 @@
return TestProtocol.stateOrdinalToString(ordinal);
}
- public void onBackPressed(Launcher launcher) {
+ /** Called when predictive back gesture is started. */
+ public void onBackStarted(Launcher launcher) {}
+
+ /**
+ * Called when back action is invoked. This can happen when:
+ * 1. back button is pressed in 3-button navigation.
+ * 2. when back is committed during back swiped (predictive or non-predictive).
+ * 3. when we programmatically perform back action.
+ */
+ public void onBackInvoked(Launcher launcher) {
if (this != NORMAL) {
- StateManager<LauncherState> lsm = launcher.getStateManager();
+ StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
LauncherState lastState = lsm.getLastState();
- lsm.goToState(lastState, forEndCallback(this::onBackPressCompleted));
+ lsm.goToState(lastState, forEndCallback(this::onBackAnimationCompleted));
}
}
/**
- * To be called if back press is completed in a launcher state.
+ * To be called if back animation is completed in a launcher state.
*
- * @param success whether back press animation was successful or canceled.
+ * @param success whether back animation was successful or canceled.
*/
- protected void onBackPressCompleted(boolean success) {
+ protected void onBackAnimationCompleted(boolean success) {
// Do nothing. To be overridden by child class.
}
@@ -447,7 +461,7 @@
*/
public void onBackProgressed(
Launcher launcher, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
- StateManager<LauncherState> lsm = launcher.getStateManager();
+ StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
LauncherState toState = lsm.getLastState();
lsm.onBackProgressed(toState, backProgress);
}
@@ -457,7 +471,7 @@
* predictive back gesture.
*/
public void onBackCancelled(Launcher launcher) {
- StateManager<LauncherState> lsm = launcher.getStateManager();
+ StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
LauncherState toState = lsm.getLastState();
lsm.onBackCancelled(toState);
}
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 3d7e11e..9944ef6 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -19,7 +19,6 @@
import android.content.Context;
import com.android.launcher3.graphics.BitmapCreationCheck;
-import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -36,7 +35,6 @@
protected void init(Context context) {
FileLog.setDir(context.getApplicationContext().getFilesDir());
- IconShape.init(context);
if (BitmapCreationCheck.ENABLED) {
BitmapCreationCheck.startTracking(context);
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 9b65a31..13062b6 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -3,12 +3,14 @@
import android.annotation.TargetApi
import android.os.Build
import android.os.Trace
+import android.util.Log
import androidx.annotation.UiThread
+import com.android.launcher3.Flags.enableSmartspaceRemovalToggle
import com.android.launcher3.LauncherConstants.TraceEvents
+import com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET
import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
import com.android.launcher3.allapps.AllAppsStore
import com.android.launcher3.config.FeatureFlags
-import com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget
import com.android.launcher3.model.BgDataModel
import com.android.launcher3.model.StringCache
import com.android.launcher3.model.data.AppInfo
@@ -28,13 +30,15 @@
import com.android.launcher3.widget.model.WidgetsListBaseEntry
import java.util.function.Predicate
+private const val TAG = "ModelCallbacks"
+
class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
var synchronouslyBoundPages = LIntSet()
var pagesToBindSynchronously = LIntSet()
private var isFirstPagePinnedItemEnabled =
- (BuildConfig.QSB_ON_FIRST_SCREEN && !FeatureFlags.ENABLE_SMARTSPACE_REMOVAL.get())
+ (BuildConfig.QSB_ON_FIRST_SCREEN && !enableSmartspaceRemovalToggle())
var stringCache: StringCache? = null
@@ -63,7 +67,15 @@
launcher.dragController.cancelDrag()
launcher.workspace.clearDropTargets()
launcher.workspace.removeAllWorkspaceScreens()
- launcher.appWidgetHolder.clearViews()
+ // Avoid clearing the widget update listeners for staying up-to-date with widget info
+ launcher.appWidgetHolder.clearWidgetViews()
+ // TODO(b/335141365): Remove this log after the bug is fixed.
+ Log.d(
+ TAG,
+ "startBinding: " +
+ "hotseat layout was vertical: ${launcher.hotseat?.isHasVerticalHotseat}" +
+ " and is setting to ${launcher.deviceProfile.isVerticalBarLayout}"
+ )
launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
TraceHelper.INSTANCE.endSection()
}
@@ -140,7 +152,10 @@
launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
TraceHelper.INSTANCE.endSection()
launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
- launcher.workspace.pageIndicator.setPauseScroll(/*pause=*/ false, deviceProfile.isTwoPanels)
+ launcher.workspace.pageIndicator.setPauseScroll(
+ /*pause=*/ false,
+ deviceProfile.isTwoPanels
+ )
}
/**
@@ -288,8 +303,7 @@
val widgetsListBaseEntry: WidgetsListBaseEntry =
launcher.popupDataProvider.allWidgets.firstOrNull { item: WidgetsListBaseEntry ->
item.mPkgItem.packageName == BuildConfig.APPLICATION_ID
- }
- ?: return
+ } ?: return
val info =
PendingAddWidgetInfo(
@@ -313,16 +327,14 @@
)
val firstScreenPosition = 0
if (
- (FeatureFlags.QSB_ON_FIRST_SCREEN &&
- isFirstPagePinnedItemEnabled &&
- !shouldShowFirstPageWidget()) &&
+ (isFirstPagePinnedItemEnabled && !SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
) {
orderedScreenIds.removeValue(FIRST_SCREEN_ID)
orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
} else if (
- (!FeatureFlags.QSB_ON_FIRST_SCREEN && !isFirstPagePinnedItemEnabled ||
- shouldShowFirstPageWidget()) && orderedScreenIds.isEmpty
+ (!isFirstPagePinnedItemEnabled || SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
+ orderedScreenIds.isEmpty
) {
// If there are no screens, we need to have an empty screen
launcher.workspace.addExtraEmptyScreens()
@@ -378,9 +390,8 @@
}
orderedScreenIds
.filterNot { screenId ->
- FeatureFlags.QSB_ON_FIRST_SCREEN &&
- isFirstPagePinnedItemEnabled &&
- !FeatureFlags.shouldShowFirstPageWidget() &&
+ isFirstPagePinnedItemEnabled &&
+ !SHOULD_SHOW_FIRST_PAGE_WIDGET &&
screenId == WorkspaceLayoutManager.FIRST_SCREEN_ID
}
.forEach { screenId ->
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 1fede56..365fbd3 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -485,7 +485,10 @@
super.onVisibilityAggregated(isVisible);
}
- protected boolean isPageInTransition() {
+ /**
+ * Returns true if the page is in the middle of transition to another page
+ */
+ public boolean isPageInTransition() {
return mIsPageInTransition;
}
@@ -785,7 +788,7 @@
if (mScroller.isFinished() && pageScrollChanged) {
// TODO(b/246283207): Remove logging once root cause of flake detected.
if (Utilities.isRunningInTestHarness() && !(this instanceof Workspace)) {
- Log.d("b/246283207", this.getClass().getSimpleName() + "#onLayout() -> "
+ Log.d("b/246283207", TAG + "#onLayout() -> "
+ "if(mScroller.isFinished() && pageScrollChanged) -> getNextPage(): "
+ getNextPage() + ", getScrollForPage(getNextPage()): "
+ getScrollForPage(getNextPage()));
@@ -826,7 +829,9 @@
// or right edge for RTL.
final int pageScroll =
mIsRtl ? childPrimaryEnd - scrollOffsetEnd : childStart - scrollOffsetStart;
- if (outPageScrolls[i] != pageScroll) {
+ // If there's more than one panel, only update scroll on leftmost panel.
+ if (outPageScrolls[i] != pageScroll
+ && (panelCount <= 1 || i == getLeftmostVisiblePageForIndex(i))) {
pageScrollChanged = true;
outPageScrolls[i] = pageScroll;
}
@@ -842,7 +847,7 @@
if (panelCount > 1) {
for (int i = 0; i < childCount; i++) {
- // In case we have multiple panels, always use left most panel's page scroll for all
+ // In case we have multiple panels, always use leftmost panel's page scroll for all
// panels on the screen.
int adjustedScroll = outPageScrolls[getLeftmostVisiblePageForIndex(i)];
if (outPageScrolls[i] != adjustedScroll) {
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 1362586..0a4fb73 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -362,7 +362,7 @@
public void onLauncherResume() {
// We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
- if (new PackageManagerHelper(mContext).getApplicationInfo(mPackageName,
+ if (PackageManagerHelper.INSTANCE.get(mContext).getApplicationInfo(mPackageName,
mDragObject.dragInfo.user, PackageManager.MATCH_UNINSTALLED_PACKAGES) == null) {
mDragObject.dragSource = mOriginal;
mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index b22b690..7484b64 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -158,6 +158,11 @@
final PointF appWidgetScale = dp.getAppWidgetScale((ItemInfo) child.getTag());
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
appWidgetScale.x, appWidgetScale.y, mBorderSpace, dp.widgetPadding);
+ } else if (isChildQsb(child)) {
+ lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
+ mBorderSpace);
+ // No need to add padding for Qsb, which is either Smartspace (actual or preview), or
+ // QsbContainerView.
} else {
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
mBorderSpace);
@@ -185,6 +190,10 @@
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
}
+ private boolean isChildQsb(View child) {
+ return child.getId() == R.id.search_container_workspace;
+ }
+
public boolean invertLayoutHorizontally() {
return mInvertIfRtl && Utilities.isRtl(getResources());
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index d44438f..19a3002 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -18,6 +18,8 @@
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
+import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
+import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
@@ -68,6 +70,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewGroup;
import android.view.animation.Interpolator;
import androidx.annotation.ChecksSdkIntAtLeast;
@@ -102,6 +105,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -130,6 +134,10 @@
@ChecksSdkIntAtLeast(api = VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "U")
public static final boolean ATLEAST_U = Build.VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE;
+ @ChecksSdkIntAtLeast(api = VERSION_CODES.VANILLA_ICE_CREAM, codename = "V")
+ public static final boolean ATLEAST_V = Build.VERSION.SDK_INT
+ >= VERSION_CODES.VANILLA_ICE_CREAM;
+
/**
* Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
*/
@@ -148,6 +156,9 @@
public static final int TRANSLATE_LEFT = 2;
public static final int TRANSLATE_RIGHT = 3;
+ public static final boolean SHOULD_SHOW_FIRST_PAGE_WIDGET =
+ enableSmartspaceAsAWidget() && WIDGET_ON_FIRST_SCREEN;
+
@IntDef({TRANSLATE_UP, TRANSLATE_DOWN, TRANSLATE_LEFT, TRANSLATE_RIGHT})
public @interface AdjustmentDirection{}
@@ -634,7 +645,7 @@
} else {
// Wrap the main icon in AID
try (LauncherIcons li = LauncherIcons.obtain(context)) {
- result = li.wrapToAdaptiveIcon(mainIcon);
+ result = li.wrapToAdaptiveIcon(mainIcon, null);
}
}
if (result == null) {
@@ -831,9 +842,26 @@
}
}
- /** Encapsulates two flag checks into a single one. */
- public static boolean enableSupportForArchiving() {
- return Flags.enableSupportForArchiving()
- || getSystemProperty("pm.archiving.enabled", "false").equals("true");
+ /**
+ * Does a depth-first search through the View hierarchy starting at root, to find a view that
+ * matches the predicate. Returns null if no View was found. View has a findViewByPredicate
+ * member function but it is currently a @hide API.
+ */
+ @Nullable
+ public static <T extends View> T findViewByPredicate(@NonNull View root,
+ @NonNull Predicate<View> predicate) {
+ if (predicate.test(root)) {
+ return (T) root;
+ }
+ if (root instanceof ViewGroup parent) {
+ int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View view = findViewByPredicate(parent.getChildAt(i), predicate);
+ if (view != null) {
+ return (T) view;
+ }
+ }
+ }
+ return null;
}
}
diff --git a/src/com/android/launcher3/UtilitiesKt.kt b/src/com/android/launcher3/UtilitiesKt.kt
new file mode 100644
index 0000000..a207d57
--- /dev/null
+++ b/src/com/android/launcher3/UtilitiesKt.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewParent
+
+object UtilitiesKt {
+
+ /**
+ * Modify [ViewGroup]'s attribute with type [T]. The overridden attribute is saved by calling
+ * [View.setTag] and can be later restored by [View.getTag].
+ *
+ * @param <T> type of [ViewGroup] attribute. For example, [T] is [Boolean] if modifying
+ * [ViewGroup.setClipChildren]
+ */
+ abstract class ViewGroupAttrModifier<T>(
+ private val targetAttrValue: T,
+ private val tagKey: Int
+ ) {
+ /**
+ * If [targetAttrValue] is different from existing view attribute returned from
+ * [getAttribute], this method will save existing attribute by calling [ViewGroup.setTag].
+ * Then call [setAttribute] to set attribute with [targetAttrValue].
+ */
+ fun saveAndChangeAttribute(viewGroup: ViewGroup) {
+ val oldAttrValue = getAttribute(viewGroup)
+ if (oldAttrValue !== targetAttrValue) {
+ viewGroup.setTag(tagKey, oldAttrValue)
+ setAttribute(viewGroup, targetAttrValue)
+ }
+ }
+
+ /** Restore saved attribute in [saveAndChangeAttribute] by calling [ViewGroup.getTag]. */
+ @Suppress("UNCHECKED_CAST")
+ fun restoreAttribute(viewGroup: ViewGroup) {
+ val oldAttrValue: T = viewGroup.getTag(tagKey) as T ?: return
+ setAttribute(viewGroup, oldAttrValue)
+ viewGroup.setTag(tagKey, null)
+ }
+
+ /** Subclass will override this method to decide how to get [ViewGroup] attribute. */
+ abstract fun getAttribute(viewGroup: ViewGroup): T
+
+ /** Subclass will override this method to decide how to set [ViewGroup] attribute. */
+ abstract fun setAttribute(viewGroup: ViewGroup, attr: T)
+ }
+
+ /** [ViewGroupAttrModifier] to call [ViewGroup.setClipChildren] to false. */
+ @JvmField
+ val CLIP_CHILDREN_FALSE_MODIFIER: ViewGroupAttrModifier<Boolean> =
+ object : ViewGroupAttrModifier<Boolean>(false, R.id.saved_clip_children_tag_id) {
+ override fun getAttribute(viewGroup: ViewGroup): Boolean {
+ return viewGroup.clipChildren
+ }
+
+ override fun setAttribute(viewGroup: ViewGroup, clipChildren: Boolean) {
+ viewGroup.clipChildren = clipChildren
+ }
+ }
+
+ /** [ViewGroupAttrModifier] to call [ViewGroup.setClipToPadding] to false. */
+ @JvmField
+ val CLIP_TO_PADDING_FALSE_MODIFIER: ViewGroupAttrModifier<Boolean> =
+ object : ViewGroupAttrModifier<Boolean>(false, R.id.saved_clip_to_padding_tag_id) {
+ override fun getAttribute(viewGroup: ViewGroup): Boolean {
+ return viewGroup.clipToPadding
+ }
+
+ override fun setAttribute(viewGroup: ViewGroup, clipToPadding: Boolean) {
+ viewGroup.clipToPadding = clipToPadding
+ }
+ }
+
+ /**
+ * Recursively call [ViewGroupAttrModifier.saveAndChangeAttribute] from [View] to its parent
+ * (direct or indirect) inclusive.
+ *
+ * [ViewGroupAttrModifier.saveAndChangeAttribute] will save the existing attribute value on each
+ * view with [View.setTag], which can be restored in [restoreAttributesOnViewTree].
+ *
+ * Note that if parent is null or not a parent of the view, this method will be applied all the
+ * way to root view.
+ *
+ * @param v child view
+ * @param parent direct or indirect parent of child view
+ * @param modifiers list of [ViewGroupAttrModifier] to modify view attribute
+ */
+ @JvmStatic
+ fun modifyAttributesOnViewTree(
+ v: View?,
+ parent: ViewParent?,
+ vararg modifiers: ViewGroupAttrModifier<*>
+ ) {
+ if (v == null) {
+ return
+ }
+ if (v is ViewGroup) {
+ for (modifier in modifiers) {
+ modifier.saveAndChangeAttribute(v)
+ }
+ }
+ if (v === parent) {
+ return
+ }
+ if (v.parent is View) {
+ modifyAttributesOnViewTree(v.parent as View, parent, *modifiers)
+ }
+ }
+
+ /**
+ * Recursively call [ViewGroupAttrModifier.restoreAttribute]} to restore view attributes
+ * previously saved in [ViewGroupAttrModifier.saveAndChangeAttribute] on view to its parent
+ * (direct or indirect) inclusive.
+ *
+ * Note that if parent is null or not a parent of the view, this method will be applied all the
+ * way to root view.
+ *
+ * @param v child view
+ * @param parent direct or indirect parent of child view
+ * @param modifiers list of [ViewGroupAttrModifier] to restore view attributes
+ */
+ @JvmStatic
+ fun restoreAttributesOnViewTree(
+ v: View?,
+ parent: ViewParent?,
+ vararg modifiers: ViewGroupAttrModifier<*>
+ ) {
+ if (v == null) {
+ return
+ }
+ if (v is ViewGroup) {
+ for (modifier in modifiers) {
+ modifier.restoreAttribute(v)
+ }
+ }
+ if (v === parent) {
+ return
+ }
+ if (v.parent is View) {
+ restoreAttributesOnViewTree(v.parent as View, parent, *modifiers)
+ }
+ }
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ca34dd1..e601a3e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,8 +16,10 @@
package com.android.launcher3;
+import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
+import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
@@ -27,10 +29,9 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
+import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
-import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
@@ -73,6 +74,7 @@
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellInfo;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper;
@@ -95,6 +97,7 @@
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
+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;
@@ -123,7 +126,6 @@
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetManagerHelper;
-import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
import com.android.launcher3.widget.util.WidgetSizes;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayTouchProxy;
@@ -240,6 +242,8 @@
public static final int REORDER_TIMEOUT = 650;
protected final Alarm mReorderAlarm = new Alarm();
private PreviewBackground mFolderCreateBg;
+ /** The underlying view that we are dragging something over. */
+ private View mDragOverView = null;
private FolderIcon mDragOverFolderIcon = null;
private boolean mCreateUserFolderOnDrop = false;
private boolean mAddToExistingFolderOnDrop = false;
@@ -522,7 +526,7 @@
}
updateChildrenLayersEnabled();
- StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+ StateManager<LauncherState, Launcher> stateManager = mLauncher.getStateManager();
stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
@@ -595,7 +599,7 @@
public void bindAndInitFirstWorkspaceScreen() {
if ((!FeatureFlags.QSB_ON_FIRST_SCREEN
|| !mLauncher.getIsFirstPagePinnedItemEnabled())
- || shouldShowFirstPageWidget()) {
+ || SHOULD_SHOW_FIRST_PAGE_WIDGET) {
mFirstPagePinnedItem = null;
return;
}
@@ -637,7 +641,7 @@
mWorkspaceScreens.clear();
// Ensure that the first page is always present
- if (!ENABLE_SMARTSPACE_REMOVAL.get()) {
+ if (!enableSmartspaceRemovalToggle()) {
bindAndInitFirstWorkspaceScreen();
}
@@ -806,7 +810,7 @@
// We don't want to remove the first screen even if it's empty because that's where
// first page pinned item would go if it gets turned back on.
- if (ENABLE_SMARTSPACE_REMOVAL.get() && screenId == FIRST_SCREEN_ID) {
+ if (enableSmartspaceRemovalToggle() && screenId == FIRST_SCREEN_ID) {
continue;
}
@@ -1035,7 +1039,7 @@
CellLayout cl = mWorkspaceScreens.valueAt(i);
// FIRST_SCREEN_ID can never be removed.
if (((!FeatureFlags.QSB_ON_FIRST_SCREEN
- || shouldShowFirstPageWidget())
+ || SHOULD_SHOW_FIRST_PAGE_WIDGET)
|| id > FIRST_SCREEN_ID)
&& cl.getShortcutsAndWidgets().getChildCount() == 0) {
removeScreens.add(id);
@@ -1739,9 +1743,6 @@
final DragView dv;
if (contentView instanceof View) {
- if (contentView instanceof LauncherAppWidgetHostView) {
- mDragController.addDragListener(new AppWidgetHostViewDragListener(mLauncher));
- }
dv = mDragController.startDrag(
contentView,
draggableView,
@@ -1872,12 +1873,9 @@
return false;
}
- boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo
- && ((WorkspaceItemInfo) dropOverView.getTag()).container
- != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
- boolean willBecomeShortcut =
- (info.itemType == ITEM_TYPE_APPLICATION ||
- info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
+ boolean aboveShortcut = Folder.willAccept(dropOverView.getTag())
+ && ((ItemInfo) dropOverView.getTag()).container != CONTAINER_HOTSEAT_PREDICTION;
+ boolean willBecomeShortcut = Folder.willAcceptItemType(info.itemType);
return (aboveShortcut && willBecomeShortcut);
}
@@ -1924,12 +1922,12 @@
mCreateUserFolderOnDrop = false;
final int screenId = getCellLayoutId(target);
- boolean aboveShortcut = (v.getTag() instanceof WorkspaceItemInfo);
- boolean willBecomeShortcut = (newView.getTag() instanceof WorkspaceItemInfo);
+ boolean aboveShortcut = Folder.willAccept(v.getTag());
+ boolean willBecomeShortcut = Folder.willAccept(newView.getTag());
if (aboveShortcut && willBecomeShortcut) {
- WorkspaceItemInfo sourceInfo = (WorkspaceItemInfo) newView.getTag();
- WorkspaceItemInfo destInfo = (WorkspaceItemInfo) v.getTag();
+ ItemInfo sourceInfo = (ItemInfo) newView.getTag();
+ ItemInfo destInfo = (ItemInfo) v.getTag();
// if the drag started here, we need to remove it from the workspace
if (!external) {
getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
@@ -1953,7 +1951,7 @@
// In order to keep everything continuous, we hand off the currently rendered
// folder background to the newly created icon. This preserves animation state.
fi.setFolderBackground(mFolderCreateBg);
- mFolderCreateBg = new PreviewBackground();
+ mFolderCreateBg = new PreviewBackground(getContext());
fi.performCreateAnimation(destInfo, v, sourceInfo, d, folderLocation, scale);
} else {
fi.prepareCreateAnimation(v);
@@ -2383,6 +2381,11 @@
if (mFolderCreateBg != null) {
mFolderCreateBg.animateToRest();
}
+
+ if (mDragOverView instanceof AppPairIcon api) {
+ api.getIconDrawableArea().onTemporaryContainerChange(null);
+ mDragOverView = null;
+ }
}
private void cleanupAddToFolder() {
@@ -2658,32 +2661,36 @@
return;
}
- final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
+ mDragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
ItemInfo info = dragObject.dragInfo;
- boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
+ boolean userFolderPending = willCreateUserFolder(info, mDragOverView, false);
if (mDragMode == DRAG_MODE_NONE && userFolderPending) {
- mFolderCreateBg = new PreviewBackground();
+ mFolderCreateBg = new PreviewBackground(getContext());
mFolderCreateBg.setup(mLauncher, mLauncher, null,
- dragOverView.getMeasuredWidth(), dragOverView.getPaddingTop());
+ mDragOverView.getMeasuredWidth(), mDragOverView.getPaddingTop());
// The full preview background should appear behind the icon
mFolderCreateBg.isClipping = false;
+ if (mDragOverView instanceof AppPairIcon api) {
+ api.getIconDrawableArea().onTemporaryContainerChange(DISPLAY_FOLDER);
+ }
+
mFolderCreateBg.animateToAccept(mDragTargetLayout, mTargetCell[0], mTargetCell[1]);
mDragTargetLayout.clearDragOutlines();
setDragMode(DRAG_MODE_CREATE_FOLDER);
if (dragObject.stateAnnouncer != null) {
dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
- .getDescriptionForDropOver(dragOverView, getContext()));
+ .getDescriptionForDropOver(mDragOverView, getContext()));
}
return;
}
- boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView);
+ boolean willAddToFolder = willAddToExistingUserFolder(info, mDragOverView);
if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
- mDragOverFolderIcon = ((FolderIcon) dragOverView);
+ mDragOverFolderIcon = ((FolderIcon) mDragOverView);
mDragOverFolderIcon.onDragEnter(info);
if (mDragTargetLayout != null) {
mDragTargetLayout.clearDragOutlines();
@@ -2692,7 +2699,7 @@
if (dragObject.stateAnnouncer != null) {
dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
- .getDescriptionForDropOver(dragOverView, getContext()));
+ .getDescriptionForDropOver(mDragOverView, getContext()));
}
return;
}
@@ -3313,7 +3320,7 @@
}
} else if (child instanceof FolderIcon) {
FolderInfo folderInfo = (FolderInfo) info;
- List<WorkspaceItemInfo> matches = folderInfo.contents.stream()
+ List<ItemInfo> matches = folderInfo.getContents().stream()
.filter(matcher)
.collect(Collectors.toList());
if (!matches.isEmpty()) {
@@ -3322,6 +3329,11 @@
((FolderIcon) child).getFolder().close(false /* animate */);
}
}
+ } else if (info instanceof AppPairInfo api) {
+ // If an app pair's member apps are being removed, delete the whole app pair.
+ if (api.anyMatch(matcher)) {
+ mLauncher.removeItem(child, info, true);
+ }
}
}
}
@@ -3373,9 +3385,9 @@
}
} else if (info instanceof FolderInfo && v instanceof FolderIcon) {
FolderInfo fi = (FolderInfo) info;
- if (fi.contents.stream().anyMatch(matcher)) {
+ if (fi.anyMatch(matcher)) {
FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (WorkspaceItemInfo si : fi.contents) {
+ for (ItemInfo si : fi.getContents()) {
folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
}
((FolderIcon) v).setDotInfo(folderDotInfo);
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index c6c38fc..f11a88f 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -55,10 +55,12 @@
int y = presenterPos.cellY;
if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|| info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
- Log.d(TAG, "add predicted icon " + child.getTag().toString() + " to home screen");
int screenId = presenterPos.screenId;
x = getHotseat().getCellXFromOrder(screenId);
y = getHotseat().getCellYFromOrder(screenId);
+ // TODO(b/335141365): Remove this log after the bug is fixed.
+ Log.d(TAG, "addInScreenFromBind: hotseat inflation with x = " + x
+ + " and y = " + y);
}
addInScreen(child, info.container, presenterPos.screenId, x, y, info.spanX, info.spanY);
}
diff --git a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
index 19d0421..29862ae 100644
--- a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
@@ -28,7 +28,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -45,6 +45,7 @@
public enum DragType {
ICON,
FOLDER,
+ APP_PAIR,
WIDGET
}
@@ -103,7 +104,7 @@
&& item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
return (item instanceof LauncherAppWidgetInfo)
- || (item instanceof FolderInfo);
+ || (item instanceof CollectionInfo);
}
@Override
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index e861d38..814d142 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -22,6 +22,8 @@
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.ButtonDropTarget;
import com.android.launcher3.CellLayout;
@@ -37,6 +39,8 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.keyboard.KeyboardDragAndDropView;
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;
@@ -53,11 +57,13 @@
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.views.OptionsPopupView.OptionItem;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.util.WidgetSizes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Launcher> {
@@ -173,7 +179,7 @@
} else if (action == MOVE) {
return beginAccessibleDrag(host, item, fromKeyboard);
} else if (action == ADD_TO_WORKSPACE) {
- return addToWorkspace(item, true);
+ return addToWorkspace(item, true /*accessibility*/, null /*finishCallback*/);
} else if (action == MOVE_TO_WORKSPACE) {
return moveToWorkspace(item);
} else if (action == RESIZE) {
@@ -313,6 +319,8 @@
mDragInfo.dragType = DragType.ICON;
if (info instanceof FolderInfo) {
mDragInfo.dragType = DragType.FOLDER;
+ } else if (info instanceof AppPairInfo) {
+ mDragInfo.dragType = DragType.APP_PAIR;
} else if (info instanceof LauncherAppWidgetInfo) {
mDragInfo.dragType = DragType.WIDGET;
}
@@ -384,13 +392,19 @@
* @param item item to be added
* @param accessibility true if the first item to be added to the workspace
* should be focused for accessibility.
+ * @param finishCallback Callback which will be run after this item has been added
+ * and the view has been transitioned to the workspace, or on failure.
*
* @return true if the item could be successfully added
*/
- public boolean addToWorkspace(ItemInfo item, boolean accessibility) {
+ public boolean addToWorkspace(ItemInfo item, boolean accessibility,
+ @Nullable Consumer<Boolean> finishCallback) {
final int[] coordinates = new int[2];
final int screenId = findSpaceOnWorkspace(item, coordinates);
if (screenId == -1) {
+ if (finishCallback != null) {
+ finishCallback.accept(false /*success*/);
+ }
return false;
}
mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
@@ -400,46 +414,64 @@
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
- bindItem(item, accessibility);
+ bindItem(info, accessibility, finishCallback);
announceConfirmation(R.string.item_added_to_workspace);
} else if (item instanceof PendingAddItemInfo) {
PendingAddItemInfo info = (PendingAddItemInfo) item;
+ if (info instanceof PendingAddWidgetInfo widgetInfo
+ && widgetInfo.bindOptions == null) {
+ widgetInfo.bindOptions = widgetInfo.getDefaultSizeOptions(mContext);
+ }
Workspace<?> workspace = mContext.getWorkspace();
- workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
- mContext.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
- screenId, coordinates, info.spanX, info.spanY);
+ workspace.post(() -> {
+ workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
+ workspace.setOnPageTransitionEndCallback(() -> {
+ mContext.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ screenId, coordinates, info.spanX, info.spanY);
+ if (finishCallback != null) {
+ finishCallback.accept(/* success= */ true);
+ }
+ });
+ });
} else if (item instanceof WorkspaceItemInfo) {
WorkspaceItemInfo info = ((WorkspaceItemInfo) item).clone();
mContext.getModelWriter().addItemToDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
- bindItem(info, accessibility);
- } else if (item instanceof FolderInfo fi) {
+ bindItem(info, accessibility, finishCallback);
+ } else if (item instanceof CollectionInfo ci) {
Workspace<?> workspace = mContext.getWorkspace();
workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
- mContext.getModelWriter().addItemToDatabase(fi,
+ mContext.getModelWriter().addItemToDatabase(ci,
LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, coordinates[0],
coordinates[1]);
- fi.contents.forEach(member -> {
- mContext.getModelWriter().addItemToDatabase(member, fi.id, -1, -1, -1);
- });
- bindItem(fi, accessibility);
+ ci.getContents().forEach(member ->
+ mContext.getModelWriter()
+ .addItemToDatabase(member, ci.id, -1, -1, -1));
+ bindItem(ci, accessibility, finishCallback);
}
}));
return true;
}
- private void bindItem(ItemInfo item, boolean focusForAccessibility) {
+ private void bindItem(ItemInfo item, boolean focusForAccessibility,
+ @Nullable Consumer<Boolean> finishCallback) {
View view = mContext.getItemInflater().inflateItem(item, mContext.getModelWriter());
if (view == null) {
+ if (finishCallback != null) {
+ finishCallback.accept(false /*success*/);
+ }
return;
}
- AnimatorSet anim = null;
- if (focusForAccessibility) {
- anim = new AnimatorSet();
- anim.addListener(forEndCallback(
- () -> view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)));
- }
+ AnimatorSet anim = new AnimatorSet();
+ anim.addListener(forEndCallback((success) -> {
+ if (focusForAccessibility) {
+ view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+ if (finishCallback != null) {
+ finishCallback.accept(success);
+ }
+ }));
mContext.bindInflatedItems(Collections.singletonList(Pair.create(item, view)), anim);
}
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index a8624dd..84d6a6f 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -23,6 +23,7 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.BaseAccessibilityDelegate.DragType;
+import com.android.launcher3.folder.Folder;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -117,7 +118,7 @@
return mContext.getString(R.string.item_moved);
} else {
ItemInfo info = (ItemInfo) child.getTag();
- if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
+ if (Folder.willAccept(info)) {
return mContext.getString(R.string.folder_created);
} else if (info instanceof FolderInfo) {
@@ -148,8 +149,8 @@
if (TextUtils.isEmpty(info.title)) {
// Find the first item in the folder.
FolderInfo folder = (FolderInfo) info;
- WorkspaceItemInfo firstItem = null;
- for (WorkspaceItemInfo shortcut : folder.contents) {
+ ItemInfo firstItem = null;
+ for (ItemInfo shortcut : folder.getContents()) {
if (firstItem == null || firstItem.rank > shortcut.rank) {
firstItem = shortcut;
}
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 6acfcd0..56a7fef 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.Flags.enableExpandingPauseWorkButton;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
import static com.android.launcher3.config.FeatureFlags.ALL_APPS_GONE_VISIBILITY;
@@ -70,7 +71,6 @@
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.R;
@@ -86,6 +86,7 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -97,6 +98,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -127,7 +129,6 @@
public static final float PULL_MULTIPLIER = .02f;
public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
- private static final int SCROLL_TO_BOTTOM_DURATION = 500;
private static final long DEFAULT_SEARCH_TRANSITION_DURATION_MS = 300;
// Render the header protection at all times to debug clipping issues.
private static final boolean DEBUG_HEADER_PROTECTION = false;
@@ -158,6 +159,7 @@
};
private final Paint mNavBarScrimPaint;
private final int mHeaderProtectionColor;
+ private final int mPrivateSpaceBottomExtraSpace;
private final Path mTmpPath = new Path();
private final RectF mTmpRectF = new RectF();
protected AllAppsPagedView mViewPager;
@@ -191,8 +193,6 @@
private int mTabsProtectionAlpha;
@Nullable private AllAppsTransitionController mAllAppsTransitionController;
- private PrivateSpaceHeaderViewController mPrivateSpaceHeaderViewController;
-
public ActivityAllAppsContainerView(Context context) {
this(context, null);
}
@@ -221,6 +221,8 @@
this,
mActivityContext.getStatsLogManager(),
UserCache.INSTANCE.get(mActivityContext));
+ mPrivateSpaceBottomExtraSpace = context.getResources().getDimensionPixelSize(
+ R.dimen.ps_extra_bottom_padding);
mAH = Arrays.asList(null, null, null);
mNavBarScrimPaint = new Paint();
mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext));
@@ -260,10 +262,6 @@
*/
protected void initContent() {
mMainAdapterProvider = mSearchUiDelegate.createMainAdapterProvider();
- if (Flags.enablePrivateSpace()) {
- mPrivateSpaceHeaderViewController =
- new PrivateSpaceHeaderViewController(this, mPrivateProfileManager);
- }
mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN,
new AlphabeticalAppsList<>(mActivityContext,
@@ -397,7 +395,7 @@
mAllAppsTransitionController = allAppsTransitionController;
}
- private void animateToSearchState(boolean goingToSearch, long durationMs) {
+ void animateToSearchState(boolean goingToSearch, long durationMs) {
if (!mSearchTransitionController.isRunning() && goingToSearch == isSearching()) {
return;
}
@@ -472,8 +470,9 @@
* @param exitSearch Whether to force exit the search state and return to A-Z apps list.
*/
public void reset(boolean animate, boolean exitSearch) {
+ // Scroll Main and Work RV to top. Search RV is done in `resetSearch`.
for (int i = 0; i < mAH.size(); i++) {
- if (mAH.get(i).mRecyclerView != null) {
+ if (i != SEARCH && mAH.get(i).mRecyclerView != null) {
mAH.get(i).mRecyclerView.scrollToTop();
}
}
@@ -487,10 +486,8 @@
// Reset the base recycler view after transitioning home.
updateHeaderScroll(0);
if (exitSearch) {
- // Reset the search bar after transitioning home.
+ // Reset the search bar and search RV after transitioning home.
MAIN_EXECUTOR.getHandler().post(mSearchUiManager::resetSearch);
- // Animate to A-Z with 0 time to reset the animation with proper state management.
- animateToSearchState(false, 0);
}
if (isSearching()) {
mWorkManager.reset();
@@ -498,26 +495,29 @@
}
/**
- * Exits search and returns to A-Z apps list. Scroll to the bottom.
+ * Exits search and returns to A-Z apps list. Scroll to the private space header.
*/
- public void resetAndScrollToBottom() {
- if (mTouchHandler != null) {
- mTouchHandler.endFastScrolling();
- }
-
- // Reset the base recycler view after transitioning home.
- updateHeaderScroll(0);
-
+ public void resetAndScrollToPrivateSpaceHeader() {
// Animate to A-Z with 0 time to reset the animation with proper state management.
+ // We can't rely on `animateToSearchState` with delay inside `resetSearch` because that will
+ // conflict with following scrolling to bottom, so we need it with 0 time here.
animateToSearchState(false, 0);
MAIN_EXECUTOR.getHandler().post(() -> {
// Reset the search bar after transitioning home.
+ // When `resetSearch` is called after `animateToSearchState` is finished, the inside
+ // `animateToSearchState` with delay is a just no-op and return early.
mSearchUiManager.resetSearch();
// Switch to the main tab
switchToTab(ActivityAllAppsContainerView.AdapterHolder.MAIN);
// Scroll to bottom
- getActiveRecyclerView().scrollToBottomWithMotion(SCROLL_TO_BOTTOM_DURATION);
+ if (mPrivateProfileManager != null) {
+ mPrivateProfileManager.scrollForHeaderToBeVisibleInContainer(
+ getActiveAppsRecyclerView(),
+ getPersonalAppList().getAdapterItems(),
+ mPrivateProfileManager.getPsHeaderHeight(),
+ mActivityContext.getDeviceProfile().allAppsCellHeightPx);
+ }
});
}
@@ -586,12 +586,6 @@
return;
}
- if (!FeatureFlags.ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES.get()) {
- RecyclerView.ItemDecoration decoration = getMainAdapterProvider().getDecorator();
- getSearchRecyclerView().removeItemDecoration(decoration);
- getSearchRecyclerView().addItemDecoration(decoration);
- }
-
// replaceAppsRVcontainer() needs to use both mUsingTabs value to remove the old view AND
// showTabs value to create new view. Hence the mUsingTabs new value assignment MUST happen
// after this call.
@@ -773,7 +767,7 @@
protected void updateHeaderScroll(int scrolledOffset) {
float prog1 = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
int headerColor = getHeaderColor(prog1);
- int tabsAlpha = mHeader.getPeripheralProtectionHeight() == 0 ? 0
+ int tabsAlpha = mHeader.getPeripheralProtectionHeight(/* expectedHeight */ false) == 0 ? 0
: (int) (Utilities.boundToRange(
(scrolledOffset + mHeader.mSnappedScrolledY) / mHeaderThreshold, 0f, 1f)
* 255);
@@ -905,7 +899,7 @@
protected BaseAllAppsAdapter<T> createAdapter(AlphabeticalAppsList<T> appsList) {
return new AllAppsGridAdapter<>(mActivityContext, getLayoutInflater(), appsList,
- mMainAdapterProvider, mPrivateSpaceHeaderViewController);
+ mMainAdapterProvider);
}
// TODO(b/216683257): Remove when Taskbar All Apps supports search.
@@ -1367,12 +1361,25 @@
invalidateHeader();
}
+ /**
+ * Set {@link Animator.AnimatorListener} on {@link mAllAppsTransitionController} to observe
+ * animation of backing out of all apps search view to all apps view.
+ */
+ public void setAllAppsSearchBackAnimatorListener(Animator.AnimatorListener listener) {
+ Preconditions.assertNotNull(mAllAppsTransitionController);
+ if (mAllAppsTransitionController == null) {
+ return;
+ }
+ mAllAppsTransitionController.setAllAppsSearchBackAnimationListener(listener);
+ }
+
public void setScrimView(ScrimView scrimView) {
mScrimView = scrimView;
}
@Override
- public void drawOnScrimWithScale(Canvas canvas, float scale) {
+ public void drawOnScrimWithScaleAndBottomOffset(
+ Canvas canvas, float scale, @Px int bottomOffsetPx) {
final View panel = mBottomSheetBackground;
final boolean hasBottomSheet = panel.getVisibility() == VISIBLE;
final float translationY = ((View) panel.getParent()).getTranslationY();
@@ -1384,6 +1391,7 @@
final float topWithScale = topNoScale + verticalScaleOffset;
final float leftWithScale = panel.getLeft() + horizontalScaleOffset;
final float rightWithScale = panel.getRight() - horizontalScaleOffset;
+ final float bottomWithOffset = panel.getBottom() + bottomOffsetPx;
// Draw full background panel for tablets.
if (hasBottomSheet) {
mHeaderPaint.setColor(mBottomSheetBackgroundColor);
@@ -1393,7 +1401,7 @@
leftWithScale,
topWithScale,
rightWithScale,
- panel.getBottom());
+ bottomWithOffset);
mTmpPath.reset();
mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
canvas.drawPath(mTmpPath, mHeaderPaint);
@@ -1435,7 +1443,7 @@
}
// If tab exist (such as work profile), extend header with tab height
- final int tabsHeight = headerView.getPeripheralProtectionHeight();
+ final int tabsHeight = headerView.getPeripheralProtectionHeight(/* expectedHeight */ false);
if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
if (DEBUG_HEADER_PROTECTION) {
mHeaderPaint.setColor(Color.BLUE);
@@ -1465,6 +1473,18 @@
}
/**
+ * The height of the header protection as if the user scrolled down the app list.
+ */
+ float getHeaderProtectionHeight() {
+ float headerBottom = getHeaderBottom() - getTranslationY();
+ if (mUsingTabs) {
+ return headerBottom + mHeader.getPeripheralProtectionHeight(/* expectedHeight */ true);
+ } else {
+ return headerBottom;
+ }
+ }
+
+ /**
* redraws header protection
*/
public void invalidateHeader() {
@@ -1485,6 +1505,10 @@
return bottom + mHeader.getTop();
}
+ boolean isUsingTabs() {
+ return mUsingTabs;
+ }
+
/**
* Returns a view that denotes the visible part of all apps container view.
*/
@@ -1552,6 +1576,14 @@
int bottomOffset = 0;
if (isWork() && mWorkManager.getWorkModeSwitch() != null) {
bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight();
+ } else if (isMain() && mPrivateProfileManager != null) {
+ Optional<AdapterItem> privateSpaceHeaderItem = mAppsList.getAdapterItems()
+ .stream()
+ .filter(item -> item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER)
+ .findFirst();
+ if (privateSpaceHeaderItem.isPresent()) {
+ bottomOffset = mPrivateSpaceBottomExtraSpace;
+ }
}
if (isSearchBarFloating()) {
bottomOffset += mSearchContainer.getHeight();
@@ -1568,5 +1600,9 @@
private boolean isSearch() {
return mType == SEARCH;
}
+
+ private boolean isMain() {
+ return mType == MAIN;
+ }
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 5f002b5..df383bf 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -73,9 +73,8 @@
public AllAppsGridAdapter(T activityContext, LayoutInflater inflater,
- AlphabeticalAppsList apps, SearchAdapterProvider<?> adapterProvider,
- PrivateSpaceHeaderViewController privateSpaceHeaderViewController) {
- super(activityContext, inflater, apps, adapterProvider, privateSpaceHeaderViewController);
+ AlphabeticalAppsList apps, SearchAdapterProvider<?> adapterProvider) {
+ super(activityContext, inflater, apps, adapterProvider);
mGridLayoutMgr = new AppsGridLayoutManager(mActivityContext);
mGridLayoutMgr.setSpanSizeLookup(new GridSpanSizer());
setAppsPerRow(activityContext.getDeviceProfile().numShownAllAppsColumns);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 36a44cc..2a47222 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -180,7 +180,7 @@
* Maps the touch (from 0..1) to the adapter position that should be visible.
*/
@Override
- public String scrollToPositionAtProgress(float touchFraction) {
+ public CharSequence scrollToPositionAtProgress(float touchFraction) {
int rowCount = mApps.getNumAppRows();
if (rowCount == 0) {
return "";
@@ -305,9 +305,7 @@
@Override
public int getScrollBarTop() {
- return ActivityContext.lookupContext(getContext()).getAppsView().isSearchSupported()
- ? getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding)
- : 0;
+ return getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding);
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index e2c5795..1b0ad04 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -24,6 +24,9 @@
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER;
+import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree;
+import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
@@ -38,8 +41,6 @@
import android.util.FloatProperty;
import android.view.HapticFeedbackConstants;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
import android.view.animation.Interpolator;
import androidx.annotation.FloatRange;
@@ -167,6 +168,8 @@
private final AnimatedFloat mAllAppScale = new AnimatedFloat(this::onScaleProgressChanged);
private final int mNavScrimFlag;
+ @Nullable private Animator.AnimatorListener mAllAppsSearchBackAnimationListener;
+
private boolean mIsVerticalLayout;
// Animation in this class is controlled by a single variable {@link mProgress}.
@@ -280,8 +283,7 @@
return;
}
- float deceleratedProgress =
- Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(backProgress);
+ float deceleratedProgress = Interpolators.BACK_GESTURE.getInterpolation(backProgress);
float scaleProgress = ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE
+ (1 - ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE)
* (1 - deceleratedProgress);
@@ -292,12 +294,11 @@
private void onScaleProgressChanged() {
final float scaleProgress = mAllAppScale.value;
SCALE_PROPERTY.set(mLauncher.getAppsView(), scaleProgress);
- mLauncher.getScrimView().setScrimHeaderScale(scaleProgress);
+ if (!mLauncher.getAppsView().isSearching() || !mLauncher.getDeviceProfile().isTablet) {
+ mLauncher.getScrimView().setScrimHeaderScale(scaleProgress);
+ }
AllAppsRecyclerView rv = mLauncher.getAppsView().getActiveRecyclerView();
- if (rv != null && rv.getScrollbar() != null) {
- rv.getScrollbar().setVisibility(scaleProgress < 1f ? View.INVISIBLE : View.VISIBLE);
- }
// Disable view clipping from all apps' RecyclerView up to all apps view during scale
// animation, and vice versa. The goal is to display extra roll(s) app icons (rendered in
@@ -306,18 +307,34 @@
if (hasScaleEffect != mHasScaleEffect) {
mHasScaleEffect = hasScaleEffect;
if (mHasScaleEffect) {
- setClipChildrenOnViewTree(rv, mLauncher.getAppsView(), false);
+ modifyAttributesOnViewTree(rv, mLauncher.getAppsView(),
+ CLIP_CHILDREN_FALSE_MODIFIER);
} else {
- restoreClipChildrenOnViewTree(rv, mLauncher.getAppsView());
+ restoreAttributesOnViewTree(rv, mLauncher.getAppsView(),
+ CLIP_CHILDREN_FALSE_MODIFIER);
}
}
}
- /** Animate all apps view to 1f scale. */
+ /** Set {@link Animator.AnimatorListener} for scaling all apps scale to 1 animation. */
+ public void setAllAppsSearchBackAnimationListener(Animator.AnimatorListener listener) {
+ mAllAppsSearchBackAnimationListener = listener;
+ }
+
+ /**
+ * Animate all apps view to 1f scale. This is called when backing (exiting) from all apps
+ * search view to all apps view.
+ */
public void animateAllAppsToNoScale() {
- mAllAppScale.animateToValue(1f)
- .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
- .start();
+ if (mAllAppScale.isAnimating()) {
+ return;
+ }
+ Animator animator = mAllAppScale.animateToValue(1f)
+ .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS);
+ if (mAllAppsSearchBackAnimationListener != null) {
+ animator.addListener(mAllAppsSearchBackAnimationListener);
+ }
+ animator.start();
}
/**
@@ -423,79 +440,6 @@
}
/**
- * Recursively call {@link ViewGroup#setClipChildren(boolean)} from {@link View} to ts parent
- * (direct or indirect) inclusive. This method will also save the old clipChildren value on each
- * view with {@link View#setTag(int, Object)}, which can be restored in
- * {@link #restoreClipChildrenOnViewTree(View, ViewParent)}.
- *
- * Note that if parent is null or not a parent of the view, this method will be applied all the
- * way to root view.
- *
- * @param v child view
- * @param parent direct or indirect parent of child view
- * @param clipChildren whether we should clip children
- */
- private static void setClipChildrenOnViewTree(
- @Nullable View v,
- @Nullable ViewParent parent,
- boolean clipChildren) {
- if (v == null) {
- return;
- }
-
- if (v instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) v;
- boolean oldClipChildren = viewGroup.getClipChildren();
- if (oldClipChildren != clipChildren) {
- v.setTag(R.id.saved_clip_children_tag_id, oldClipChildren);
- viewGroup.setClipChildren(clipChildren);
- }
- }
-
- if (v == parent) {
- return;
- }
-
- if (v.getParent() instanceof View) {
- setClipChildrenOnViewTree((View) v.getParent(), parent, clipChildren);
- }
- }
-
- /**
- * Recursively call {@link ViewGroup#setClipChildren(boolean)} to restore clip children value
- * set in {@link #setClipChildrenOnViewTree(View, ViewParent, boolean)} on view to its parent
- * (direct or indirect) inclusive.
- *
- * Note that if parent is null or not a parent of the view, this method will be applied all the
- * way to root view.
- *
- * @param v child view
- * @param parent direct or indirect parent of child view
- */
- private static void restoreClipChildrenOnViewTree(
- @Nullable View v, @Nullable ViewParent parent) {
- if (v == null) {
- return;
- }
- if (v instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) v;
- Object viewTag = viewGroup.getTag(R.id.saved_clip_children_tag_id);
- if (viewTag instanceof Boolean) {
- viewGroup.setClipChildren((boolean) viewTag);
- viewGroup.setTag(R.id.saved_clip_children_tag_id, null);
- }
- }
-
- if (v == parent) {
- return;
- }
-
- if (v.getParent() instanceof View) {
- restoreClipChildrenOnViewTree((View) v.getParent(), parent);
- }
- }
-
- /**
* Updates the total scroll range but does not update the UI.
*/
public void setShiftRange(float shiftRange) {
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index fba7537..5d03a93 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -18,14 +18,20 @@
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT;
import android.content.Context;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ImageSpan;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.DiffUtil;
import com.android.launcher3.Flags;
+import com.android.launcher3.R;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -62,11 +68,11 @@
*/
public static class FastScrollSectionInfo {
// The section name
- public final String sectionName;
+ public final CharSequence sectionName;
// The item position
public final int position;
- public FastScrollSectionInfo(String sectionName, int position) {
+ public FastScrollSectionInfo(CharSequence sectionName, int position) {
this.sectionName = sectionName;
this.position = position;
}
@@ -90,6 +96,7 @@
// The of ordered component names as a result of a search query
private final ArrayList<AdapterItem> mSearchResults = new ArrayList<>();
+ private final SpannableString mPrivateProfileAppScrollerBadge;
private BaseAllAppsAdapter<T> mAdapter;
private AppInfoComparator mAppNameComparator;
private int mNumAppsPerRowAllApps;
@@ -107,6 +114,10 @@
if (mAllAppsStore != null) {
mAllAppsStore.addUpdateListener(this);
}
+ mPrivateProfileAppScrollerBadge = new SpannableString(" ");
+ mPrivateProfileAppScrollerBadge.setSpan(new ImageSpan(context,
+ R.drawable.ic_private_profile_app_scroller_badge, ImageSpan.ALIGN_CENTER),
+ 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
/** Set the number of apps per row when device profile changes. */
@@ -203,7 +214,10 @@
*/
@Override
public void onAppsUpdated() {
- if (mAllAppsStore == null) {
+ // Don't update apps when the private profile animations are running, otherwise the motion
+ // is canceled.
+ if (mAllAppsStore == null || (mPrivateProviderManager != null &&
+ mPrivateProviderManager.getAnimationRunning())) {
return;
}
// Sort the list of apps
@@ -270,6 +284,12 @@
addApps = mWorkProviderManager.shouldShowWorkApps();
}
if (addApps) {
+ if (/* education card was added */ position == 1) {
+ // Add work educard section with "info icon" at 0th position.
+ mFastScrollerSections.add(new FastScrollSectionInfo(
+ mActivityContext.getResources().getString(
+ R.string.work_profile_edu_section), 0));
+ }
position = addAppsWithSections(mApps, position);
}
if (Flags.enablePrivateSpace()) {
@@ -317,6 +337,8 @@
&& !mPrivateApps.isEmpty()) {
// Always add PS Header if Space is present and visible.
position = mPrivateProviderManager.addPrivateSpaceHeader(mAdapterItems);
+ mFastScrollerSections.add(new FastScrollSectionInfo(
+ mPrivateProfileAppScrollerBadge, position));
int privateSpaceState = mPrivateProviderManager.getCurrentState();
switch (privateSpaceState) {
case PrivateProfileManager.STATE_DISABLED:
@@ -341,7 +363,21 @@
// Split of private space apps into user-installed and system apps.
Map<Boolean, List<AppInfo>> split = mPrivateApps.stream()
.collect(Collectors.partitioningBy(mPrivateProviderManager
- .splitIntoUserInstalledAndSystemApps()));
+ .splitIntoUserInstalledAndSystemApps(mActivityContext)));
+
+ // TODO(b/329688630): switch to the pulled LayoutStaticSnapshot atom
+ mActivityContext
+ .getStatsLogManager()
+ .logger()
+ .withCardinality(split.get(true).size())
+ .log(LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT);
+
+ mActivityContext
+ .getStatsLogManager()
+ .logger()
+ .withCardinality(split.get(false).size())
+ .log(LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT);
+
// Add user installed apps
position = addAppsWithSections(split.get(true), position);
// Add system apps separator.
@@ -357,6 +393,7 @@
private int addAppsWithSections(List<AppInfo> appList, int startPosition) {
String lastSectionName = null;
boolean hasPrivateApps = false;
+ int position = startPosition;
if (mPrivateProviderManager != null) {
hasPrivateApps = appList.stream().
allMatch(mPrivateProviderManager.getItemInfoMatcher());
@@ -377,11 +414,12 @@
// Create a new section if the section names do not match
if (!sectionName.equals(lastSectionName)) {
lastSectionName = sectionName;
- mFastScrollerSections.add(new FastScrollSectionInfo(sectionName, startPosition));
+ mFastScrollerSections.add(new FastScrollSectionInfo(hasPrivateApps ?
+ mPrivateProfileAppScrollerBadge : sectionName, position));
}
- startPosition++;
+ position++;
}
- return startPosition;
+ return position;
}
/**
@@ -421,6 +459,10 @@
return roundRegion;
}
+ public PrivateProfileManager getPrivateProfileManager() {
+ return mPrivateProviderManager;
+ }
+
private static class MyDiffCallback extends DiffUtil.Callback {
private final List<AdapterItem> mOldList;
diff --git a/src/com/android/launcher3/allapps/AppInfoComparator.java b/src/com/android/launcher3/allapps/AppInfoComparator.java
index a0867db..bbf8e5a 100644
--- a/src/com/android/launcher3/allapps/AppInfoComparator.java
+++ b/src/com/android/launcher3/allapps/AppInfoComparator.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.os.Process;
import android.os.UserHandle;
+import android.text.TextUtils;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.pm.UserCache;
@@ -64,7 +65,7 @@
}
private String getSortingTitle(AppInfo info) {
- if (info.appTitle != null) {
+ if (!TextUtils.isEmpty(info.appTitle)) {
return info.appTitle.toString();
}
if (info.title != null) {
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 5e5795d..98ca420 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -15,12 +15,15 @@
*/
package com.android.launcher3.allapps;
+import static android.view.View.GONE;
+
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_TOP_LEFT;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_TOP_RIGHT;
import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
import android.content.Context;
import android.view.LayoutInflater;
@@ -40,7 +43,6 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.views.ActivityContext;
@@ -169,16 +171,9 @@
protected final OnClickListener mOnIconClickListener;
protected final OnLongClickListener mOnIconLongClickListener;
protected OnFocusChangeListener mIconFocusListener;
- private final PrivateSpaceHeaderViewController mPrivateSpaceHeaderViewController;
public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) {
- this(activityContext, inflater, apps, adapterProvider, null);
- }
-
- public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
- AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider,
- PrivateSpaceHeaderViewController privateSpaceHeaderViewController) {
mActivityContext = activityContext;
mApps = apps;
mLayoutInflater = inflater;
@@ -187,7 +182,6 @@
mOnIconLongClickListener = mActivityContext.getAllAppsItemLongClickListener();
mAdapterProvider = adapterProvider;
- mPrivateSpaceHeaderViewController = privateSpaceHeaderViewController;
}
/** Checks if the passed viewType represents all apps divider. */
@@ -270,6 +264,29 @@
icon.reset();
icon.applyFromApplicationInfo(adapterItem.itemInfo);
icon.setOnFocusChangeListener(mIconFocusListener);
+ PrivateProfileManager privateProfileManager = mApps.getPrivateProfileManager();
+ if (privateProfileManager != null) {
+ // Set the alpha of the private space icon to 0 upon expanding the header so the
+ // alpha can animate -> 1. This should only be in effect when doing a
+ // transitioning between Locked/Unlocked state.
+ boolean isPrivateSpaceItem =
+ privateProfileManager.isPrivateSpaceItem(adapterItem);
+ if (icon.getAlpha() == 0 || icon.getAlpha() == 1) {
+ icon.setAlpha(isPrivateSpaceItem
+ && privateProfileManager.isStateTransitioning()
+ && (privateProfileManager.isScrolling() ||
+ privateProfileManager.getReadyToAnimate())
+ && privateProfileManager.getCurrentState() == STATE_ENABLED
+ ? 0 : 1);
+ }
+ // Views can still be bounded before the app list is updated hence showing icons
+ // after collapsing.
+ if (privateProfileManager.getCurrentState() == STATE_DISABLED
+ && isPrivateSpaceItem) {
+ adapterItem.decorationInfo = null;
+ icon.setVisibility(GONE);
+ }
+ }
break;
}
case VIEW_TYPE_EMPTY_SEARCH: {
@@ -283,13 +300,10 @@
case VIEW_TYPE_PRIVATE_SPACE_HEADER:
RelativeLayout psHeaderLayout = holder.itemView.findViewById(
R.id.ps_header_layout);
- assert mPrivateSpaceHeaderViewController != null;
- assert psHeaderLayout != null;
- mPrivateSpaceHeaderViewController.addPrivateSpaceHeaderViewElements(psHeaderLayout);
+ mApps.getPrivateProfileManager().bindPrivateSpaceHeaderViewElements(psHeaderLayout);
AdapterItem adapterItem = mApps.getAdapterItems().get(position);
int roundRegions = ROUND_TOP_LEFT | ROUND_TOP_RIGHT;
- if (mPrivateSpaceHeaderViewController.getPrivateProfileManager().getCurrentState()
- == STATE_DISABLED) {
+ if (mApps.getPrivateProfileManager().getCurrentState() == STATE_DISABLED) {
roundRegions |= (ROUND_BOTTOM_LEFT | ROUND_BOTTOM_RIGHT);
}
adapterItem.decorationInfo =
@@ -298,7 +312,8 @@
break;
case VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER:
adapterItem = mApps.getAdapterItems().get(position);
- adapterItem.decorationInfo = new SectionDecorationInfo(mActivityContext,
+ adapterItem.decorationInfo = mApps.getPrivateProfileManager().getCurrentState()
+ == STATE_DISABLED ? null : new SectionDecorationInfo(mActivityContext,
ROUND_NOTHING, true /* decorateTogether */);
break;
case VIEW_TYPE_ALL_APPS_DIVIDER:
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index a1f6ebe..92c589c 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -34,7 +34,7 @@
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.views.ActivityContext;
import com.android.systemui.plugins.AllAppsRow;
import com.android.systemui.plugins.AllAppsRow.OnHeightUpdatedListener;
@@ -466,9 +466,14 @@
}
/**
- * Returns visible height of FloatingHeaderView contents requiring header protection
+ * Returns visible height of FloatingHeaderView contents requiring header protection or the
+ * expected header protection height.
*/
- int getPeripheralProtectionHeight() {
+ int getPeripheralProtectionHeight(boolean expected) {
+ if (expected) {
+ return getTabLayout().getBottom() - getPaddingTop() + getPaddingBottom()
+ - mMaxTranslation;
+ }
// we only want to show protection when work tab is available and header is either
// collapsed or animating to/from collapsed state
if (mTabsHidden || mFloatingRowsCollapsed || !mHeaderCollapsed) {
diff --git a/src/com/android/launcher3/allapps/FloatingMaskView.java b/src/com/android/launcher3/allapps/FloatingMaskView.java
new file mode 100644
index 0000000..606eb03
--- /dev/null
+++ b/src/com/android/launcher3/allapps/FloatingMaskView.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.views.ActivityContext;
+
+public class FloatingMaskView extends ConstraintLayout {
+
+ private final ActivityContext mActivityContext;
+ private ImageView mBottomBox;
+
+ public FloatingMaskView(Context context) {
+ this(context, null, 0);
+ }
+
+ public FloatingMaskView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FloatingMaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mActivityContext = ActivityContext.lookupContext(context);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mBottomBox = findViewById(R.id.bottom_box);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
+ AllAppsRecyclerView allAppsContainerView =
+ mActivityContext.getAppsView().getActiveRecyclerView();
+ if (lp != null) {
+ lp.rightMargin = allAppsContainerView.getPaddingRight();
+ lp.leftMargin = allAppsContainerView.getPaddingLeft();
+ mBottomBox.setMinimumHeight(allAppsContainerView.getPaddingBottom());
+ }
+ }
+}
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 63a168e..1e0f289 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -56,7 +56,7 @@
return false;
}
Launcher launcher = mActivityContext;
- StateManager<LauncherState> manager = launcher.getStateManager();
+ StateManager<LauncherState, Launcher> manager = launcher.getStateManager();
if (manager.isInTransition() && manager.getTargetState() != null) {
return manager.getTargetState().shouldFloatingSearchBarUsePillWhenUnfocused(launcher);
}
@@ -70,7 +70,7 @@
return super.getFloatingSearchBarRestingMarginBottom();
}
Launcher launcher = mActivityContext;
- StateManager<LauncherState> stateManager = launcher.getStateManager();
+ StateManager<LauncherState, Launcher> stateManager = launcher.getStateManager();
// We want to rest at the current state's resting position, unless we are in transition and
// the target state's resting position is higher (that way if we are closing the keyboard,
@@ -95,7 +95,7 @@
return super.getFloatingSearchBarRestingMarginStart();
}
- StateManager<LauncherState> stateManager = mActivityContext.getStateManager();
+ StateManager<LauncherState, Launcher> stateManager = mActivityContext.getStateManager();
// Special case to not expand the search bar when exiting All Apps on phones.
if (stateManager.getCurrentStableState() == LauncherState.ALL_APPS
@@ -117,7 +117,7 @@
return super.getFloatingSearchBarRestingMarginEnd();
}
- StateManager<LauncherState> stateManager = mActivityContext.getStateManager();
+ StateManager<LauncherState, Launcher> stateManager = mActivityContext.getStateManager();
// Special case to not expand the search bar when exiting All Apps on phones.
if (stateManager.getCurrentStableState() == LauncherState.ALL_APPS
diff --git a/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java b/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java
index 8712b84..339e443 100644
--- a/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java
@@ -43,6 +43,9 @@
for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
+ if (position < 0 || position >= mAppsList.getAdapterItems().size()) {
+ continue;
+ }
BaseAllAppsAdapter.AdapterItem adapterItem = mAppsList.getAdapterItems().get(position);
SectionDecorationInfo info = adapterItem.decorationInfo;
if (info == null) {
@@ -56,7 +59,7 @@
new SectionDecorationHandler.UnionDecorationHandler(
decorationHandler, parent.getPaddingLeft(),
parent.getPaddingRight()));
- unionHandler.addChild(decorationHandler, view, true /* applyBackground */);
+ unionHandler.addChild(decorationHandler, view);
deferredDecorations.put(PRIVATE_APP_SECTION, unionHandler);
} else {
decorationHandler.onFocusDraw(c, view);
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 1ebd49e..6f021ea 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -16,42 +16,72 @@
package com.android.launcher3.allapps;
+import static android.view.View.GONE;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ICON;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_LOCK_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_PRIVATE_SPACE_INSTALL_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.UserHandle;
import android.os.UserManager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.recyclerview.widget.LinearSmoothScroller;
+import androidx.recyclerview.widget.RecyclerView;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.BuildConfig;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedPropertySetter;
+import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.PrivateSpaceInstallAppButtonInfo;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.RecyclerViewFastScroller;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.function.Predicate;
/**
@@ -59,18 +89,50 @@
* logic in the Personal tab.
*/
public class PrivateProfileManager extends UserProfileManager {
-
- // TODO (b/324573634): Fix the intent string.
- public static final Intent PRIVATE_SPACE_INTENT = new
- Intent("com.android.settings.action.PRIVATE_SPACE_SETUP_FLOW");
-
+ private static final int EXPAND_COLLAPSE_DURATION = 800;
+ private static final int SETTINGS_OPACITY_DURATION = 400;
+ private static final int TEXT_UNLOCK_OPACITY_DURATION = 300;
+ private static final int TEXT_LOCK_OPACITY_DURATION = 50;
+ private static final int APP_OPACITY_DURATION = 400;
+ private static final int MASK_VIEW_DURATION = 200;
+ private static final int APP_OPACITY_DELAY = 400;
+ private static final int SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY = 400;
+ private static final int SETTINGS_OPACITY_DELAY = 400;
+ private static final int LOCK_TEXT_OPACITY_DELAY = 500;
+ private static final int MASK_VIEW_DELAY = 400;
+ private static final int NO_DELAY = 0;
+ private static final int CONTAINER_OPACITY_DURATION = 150;
private final ActivityAllAppsContainerView<?> mAllApps;
private final Predicate<UserHandle> mPrivateProfileMatcher;
- private Set<String> mPreInstalledSystemPackages = new HashSet<>();
+ private final int mPsHeaderHeight;
+ private final int mFloatingMaskViewCornerRadius;
+ private final RecyclerView.OnScrollListener mOnIdleScrollListener =
+ new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
+ super.onScrollStateChanged(recyclerView, newState);
+ if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ mIsScrolling = false;
+ }
+ }
+ };
private Intent mAppInstallerIntent = new Intent();
private PrivateAppsSectionDecorator mPrivateAppsSectionDecorator;
private boolean mPrivateSpaceSettingsAvailable;
- private Runnable mUnlockRunnable;
+ // Returns if the animation is currently running.
+ private boolean mIsAnimationRunning;
+ // mAnimate denotes if private space is ready to be animated.
+ private boolean mReadyToAnimate;
+ // Returns when the recyclerView is currently scrolling.
+ private boolean mIsScrolling;
+ // mIsStateTransitioning indicates that private space is transitioning between states.
+ private boolean mIsStateTransitioning;
+ private Runnable mOnPSHeaderAdded;
+ @Nullable
+ private RelativeLayout mPSHeader;
+ private ConstraintLayout mFloatingMaskView;
+ private final String mLockedStateContentDesc;
+ private final String mUnLockedStateContentDesc;
public PrivateProfileManager(UserManager userManager,
ActivityAllAppsContainerView<?> allApps,
@@ -79,7 +141,17 @@
super(userManager, statsLogManager, userCache);
mAllApps = allApps;
mPrivateProfileMatcher = (user) -> userCache.getUserInfo(user).isPrivate();
- UI_HELPER_EXECUTOR.post(this::initializeInBackgroundThread);
+
+ Context appContext = allApps.getContext().getApplicationContext();
+ UI_HELPER_EXECUTOR.post(() -> initializeInBackgroundThread(appContext));
+ mPsHeaderHeight = mAllApps.getContext().getResources().getDimensionPixelSize(
+ R.dimen.ps_header_height);
+ mLockedStateContentDesc = mAllApps.getContext()
+ .getString(R.string.ps_container_lock_button_content_description);
+ mUnLockedStateContentDesc = mAllApps.getContext()
+ .getString(R.string.ps_container_unlock_button_content_description);
+ mFloatingMaskViewCornerRadius = mAllApps.getContext().getResources().getDimensionPixelSize(
+ R.dimen.ps_floating_mask_corner_radius);
}
/** Adds Private Space Header to the layout. */
@@ -105,13 +177,13 @@
context, com.android.launcher3.R.drawable.private_space_install_app_icon);
BitmapInfo bitmapInfo = LauncherIcons.obtain(context).createIconBitmap(shortcut);
- AppInfo itemInfo = new AppInfo();
+ PrivateSpaceInstallAppButtonInfo itemInfo = new PrivateSpaceInstallAppButtonInfo();
itemInfo.title = context.getResources().getString(R.string.ps_add_button_label);
itemInfo.intent = mAppInstallerIntent;
itemInfo.bitmap = bitmapInfo;
itemInfo.contentDescription = context.getResources().getString(
com.android.launcher3.R.string.ps_add_button_content_description);
- itemInfo.runtimeStatusFlags |= FLAG_PRIVATE_SPACE_INSTALL_APP | FLAG_NOT_PINNABLE;
+ itemInfo.runtimeStatusFlags |= FLAG_NOT_PINNABLE;
BaseAllAppsAdapter.AdapterItem item = new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_ICON);
item.itemInfo = itemInfo;
@@ -122,48 +194,37 @@
mAllApps.mAH.get(MAIN).mAdapter.notifyItemInserted(adapterItems.size() - 1);
}
- /**
- * Disables quiet mode for Private Space User Profile.
- * The runnable passed will be executed in the {@link #reset()} method,
- * when Launcher receives update about profile availability.
- * The runnable passed is only executed once, and reset after execution.
- * In case the method is called again, before the previously set runnable was executed,
- * the runnable will be updated.
- */
- public void unlockPrivateProfile(Runnable runnable) {
- enableQuietMode(false);
- mUnlockRunnable = runnable;
- }
-
- /** Enables quiet mode for Private Space User Profile. */
- public void lockPrivateProfile() {
- enableQuietMode(true);
- }
-
/** Whether private profile should be hidden on Launcher. */
public boolean isPrivateSpaceHidden() {
return getCurrentState() == STATE_DISABLED && SettingsCache.INSTANCE
- .get(mAllApps.mActivityContext).getValue(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, 0);
+ .get(mAllApps.getContext()).getValue(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, 0);
}
- /** Resets the current state of Private Profile, w.r.t. to Launcher. */
+ /**
+ * Resets the current state of Private Profile, w.r.t. to Launcher. The decorator should only
+ * be applied upon expand before animating. When collapsing, reset() will remove the decorator
+ * when animation is not running.
+ */
public void reset() {
+ // Ensure the state of the header views is what it should be before animating.
+ updateView();
+ getMainRecyclerView().setChildAttachedConsumer(null);
int previousState = getCurrentState();
boolean isEnabled = !mAllApps.getAppsStore()
.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED);
int updatedState = isEnabled ? STATE_ENABLED : STATE_DISABLED;
setCurrentState(updatedState);
- resetPrivateSpaceDecorator(updatedState);
- if (transitioningFromLockedToUnlocked(previousState, updatedState)) {
- applyUnlockRunnable();
+ if (Flags.privateSpaceAddFloatingMaskView()) {
+ mFloatingMaskView = null;
}
- }
-
- /** Opens the Private Space Settings Page. */
- public void openPrivateSpaceSettings() {
- if (mPrivateSpaceSettingsAvailable) {
- mAllApps.getContext().startActivity(PRIVATE_SPACE_INTENT);
+ // It's possible that previousState is 0 when reset is first called.
+ mIsStateTransitioning = previousState != STATE_UNKNOWN && previousState != updatedState;
+ if (previousState == STATE_DISABLED && updatedState == STATE_ENABLED) {
+ postUnlock();
+ } else if (previousState == STATE_ENABLED && updatedState == STATE_DISABLED){
+ executeLock();
}
+ addPrivateSpaceDecorator(updatedState);
}
/** Returns whether or not Private Space Settings Page is available. */
@@ -185,38 +246,20 @@
* This case should still be ok, as locking the Private Space container and unlocking it,
* reloads the values, fixing the incorrect UI.
*/
- private void initializeInBackgroundThread() {
+ private void initializeInBackgroundThread(Context appContext) {
Preconditions.assertNonUiThread();
- setPreInstalledSystemPackages();
- setAppInstallerIntent();
- initializePrivateSpaceSettingsState();
- }
-
- private void initializePrivateSpaceSettingsState() {
- Preconditions.assertNonUiThread();
- ResolveInfo resolveInfo = mAllApps.getContext().getPackageManager()
- .resolveActivity(PRIVATE_SPACE_INTENT, PackageManager.MATCH_SYSTEM_ONLY);
- setPrivateSpaceSettingsAvailable(resolveInfo != null);
- }
-
- private void setPreInstalledSystemPackages() {
- Preconditions.assertNonUiThread();
- if (getProfileUser() != null) {
- mPreInstalledSystemPackages = new HashSet<>(ApiWrapper
- .getPreInstalledSystemPackages(mAllApps.getContext(), getProfileUser()));
+ ApiWrapper apiWrapper = ApiWrapper.INSTANCE.get(appContext);
+ UserHandle profileUser = getProfileUser();
+ if (profileUser != null) {
+ mAppInstallerIntent = apiWrapper
+ .getAppMarketActivityIntent(BuildConfig.APPLICATION_ID, profileUser);
}
+ setPrivateSpaceSettingsAvailable(apiWrapper.getPrivateSpaceSettingsIntent() != null);
}
- private void setAppInstallerIntent() {
- Preconditions.assertNonUiThread();
- if (getProfileUser() != null) {
- mAppInstallerIntent = ApiWrapper.getAppMarketActivityIntent(mAllApps.getContext(),
- BuildConfig.APPLICATION_ID, getProfileUser());
- }
- }
-
+ /** Adds a private space decorator only when STATE_ENABLED. */
@VisibleForTesting
- void resetPrivateSpaceDecorator(int updatedState) {
+ void addPrivateSpaceDecorator(int updatedState) {
ActivityAllAppsContainerView<?>.AdapterHolder mainAdapterHolder = mAllApps.mAH.get(MAIN);
if (updatedState == STATE_ENABLED) {
// Create a new decorator instance if not already available.
@@ -233,29 +276,59 @@
}
// Add Private Space Decorator to the Recycler view.
mainAdapterHolder.mRecyclerView.addItemDecoration(mPrivateAppsSectionDecorator);
+ }
+ }
+
+ @Override
+ public void setQuietMode(boolean enable) {
+ UI_HELPER_EXECUTOR.post(() ->
+ mUserCache.getUserProfiles()
+ .stream()
+ .filter(getUserMatcher())
+ .findFirst()
+ .ifPresent(userHandle -> setQuietModeSafely(enable, userHandle)));
+ mReadyToAnimate = true;
+ }
+
+ /**
+ * Sets Quiet Mode for Private Profile.
+ * If {@link SecurityException} is thrown, prompts the user to set this launcher as HOME app.
+ */
+ private void setQuietModeSafely(boolean enable, UserHandle userHandle) {
+ try {
+ mUserManager.requestQuietModeEnabled(enable, userHandle);
+ } catch (SecurityException ex) {
+ ApiWrapper.INSTANCE.get(mAllApps.mActivityContext)
+ .assignDefaultHomeRole(mAllApps.mActivityContext);
+ }
+ }
+
+ /**
+ * Expand the private space after the app list has been added and updated from
+ * {@link AlphabeticalAppsList#onAppsUpdated()}
+ */
+ void postUnlock() {
+ if (mAllApps.isSearching()) {
+ MAIN_EXECUTOR.post(this::exitSearchAndExpand);
} else {
- // Remove Private Space Decorator from the Recycler view.
- if (mPrivateAppsSectionDecorator != null) {
- mainAdapterHolder.mRecyclerView.removeItemDecoration(mPrivateAppsSectionDecorator);
- }
+ MAIN_EXECUTOR.post(this::expandPrivateSpace);
}
}
- /** Posts quiet mode enable/disable call for private profile. */
- private void enableQuietMode(boolean enable) {
- setQuietMode(enable);
+ /** Collapses the private space before the app list has been updated. */
+ void executeLock() {
+ MAIN_EXECUTOR.execute(() -> updatePrivateStateAnimator(false));
}
- void applyUnlockRunnable() {
- if (mUnlockRunnable != null) {
- // reset the runnable to prevent re-execution.
- MAIN_EXECUTOR.post(mUnlockRunnable);
- mUnlockRunnable = null;
+ void setAnimationRunning(boolean isAnimationRunning) {
+ if (!isAnimationRunning) {
+ mReadyToAnimate = false;
}
+ mIsAnimationRunning = isAnimationRunning;
}
- private boolean transitioningFromLockedToUnlocked(int previousState, int updatedState) {
- return previousState == STATE_DISABLED && updatedState == STATE_ENABLED;
+ boolean getAnimationRunning() {
+ return mIsAnimationRunning;
}
@Override
@@ -267,9 +340,528 @@
* Splits private apps into user installed and system apps.
* When the list of system apps is empty, all apps are treated as system.
*/
- public Predicate<AppInfo> splitIntoUserInstalledAndSystemApps() {
- return appInfo -> !mPreInstalledSystemPackages.isEmpty()
+ public Predicate<AppInfo> splitIntoUserInstalledAndSystemApps(Context context) {
+ List<String> preInstallApps = UserCache.getInstance(context)
+ .getPreInstallApps(getProfileUser());
+ return appInfo -> !preInstallApps.isEmpty()
&& (appInfo.componentName == null
- || !(mPreInstalledSystemPackages.contains(appInfo.componentName.getPackageName())));
+ || !(preInstallApps.contains(appInfo.componentName.getPackageName())));
+ }
+
+ /** Add Private Space Header view elements based upon {@link UserProfileState} */
+ public void bindPrivateSpaceHeaderViewElements(RelativeLayout parent) {
+ mPSHeader = parent;
+ if (mOnPSHeaderAdded != null) {
+ MAIN_EXECUTOR.execute(mOnPSHeaderAdded);
+ mOnPSHeaderAdded = null;
+ }
+ // Set the transition duration for the settings and lock button to animate.
+ ViewGroup settingAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
+ if (mReadyToAnimate) {
+ enableLayoutTransition(settingAndLockGroup);
+ } else {
+ // Ensure any unwanted animations to not happen.
+ settingAndLockGroup.setLayoutTransition(null);
+ }
+ updateView();
+ }
+
+ /** Update the states of the views that make up the header at the state it is called in. */
+ private void updateView() {
+ if (mPSHeader == null) {
+ return;
+ }
+ mPSHeader.setAlpha(1);
+ ViewGroup lockPill = mPSHeader.findViewById(R.id.ps_lock_unlock_button);
+ assert lockPill != null;
+ TextView lockText = lockPill.findViewById(R.id.lock_text);
+ PrivateSpaceSettingsButton settingsButton = mPSHeader.findViewById(R.id.ps_settings_button);
+ assert settingsButton != null;
+ //Add image for private space transitioning view
+ ImageView transitionView = mPSHeader.findViewById(R.id.ps_transition_image);
+ assert transitionView != null;
+ switch(getCurrentState()) {
+ case STATE_ENABLED -> {
+ mPSHeader.setOnClickListener(null);
+ mPSHeader.setClickable(false);
+ // Remove header from accessibility target when enabled.
+ mPSHeader.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+
+ lockText.setVisibility(VISIBLE);
+ lockPill.setVisibility(VISIBLE);
+ lockPill.setOnClickListener(view -> lockingAction(/* lock */ true));
+ lockPill.setContentDescription(mUnLockedStateContentDesc);
+
+ settingsButton.setVisibility(isPrivateSpaceSettingsAvailable() ? VISIBLE : GONE);
+ transitionView.setVisibility(GONE);
+ }
+ case STATE_DISABLED -> {
+ mPSHeader.setOnClickListener(view -> lockingAction(/* lock */ false));
+ mPSHeader.setClickable(true);
+ // Add header as accessibility target when disabled.
+ mPSHeader.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ mPSHeader.setContentDescription(mLockedStateContentDesc);
+
+ lockText.setVisibility(GONE);
+ lockPill.setVisibility(VISIBLE);
+ lockPill.setOnClickListener(view -> lockingAction(/* lock */ false));
+ lockPill.setContentDescription(mLockedStateContentDesc);
+
+ settingsButton.setVisibility(GONE);
+ transitionView.setVisibility(GONE);
+ }
+ case STATE_TRANSITION -> {
+ transitionView.setVisibility(VISIBLE);
+ lockPill.setVisibility(GONE);
+ }
+ }
+ }
+
+ /** Sets the enablement of the profile when header or button is clicked. */
+ private void lockingAction(boolean lock) {
+ logEvents(lock ? LAUNCHER_PRIVATE_SPACE_LOCK_TAP : LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP);
+ setQuietMode(lock);
+ }
+
+ /** Finds the private space header to scroll to and set the private space icons to GONE. */
+ private void collapse() {
+ AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
+ List<BaseAllAppsAdapter.AdapterItem> appListAdapterItems =
+ allAppsRecyclerView.getApps().getAdapterItems();
+ for (int i = appListAdapterItems.size() - 1; i > 0; i--) {
+ BaseAllAppsAdapter.AdapterItem currentItem = appListAdapterItems.get(i);
+ // Scroll to the private space header.
+ if (currentItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
+ // Note: SmoothScroller is meant to be used once.
+ RecyclerView.SmoothScroller smoothScroller =
+ new LinearSmoothScroller(mAllApps.getContext()) {
+ @Override protected int getVerticalSnapPreference() {
+ return LinearSmoothScroller.SNAP_TO_END;
+ }
+ };
+ // If privateSpaceHidden() then the entire container decorator will be invisible and
+ // we can directly move to an element above the header. There should always be one
+ // element, as PS is present in the bottom of All Apps.
+ smoothScroller.setTargetPosition(isPrivateSpaceHidden() ? i - 1 : i);
+ RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
+ if (layoutManager != null) {
+ startAnimationScroll(allAppsRecyclerView, layoutManager, smoothScroller);
+ // Preserve decorator if floating mask view exists.
+ if (mFloatingMaskView == null) {
+ currentItem.decorationInfo = null;
+ }
+ }
+ break;
+ }
+ // Make the private space apps gone to "collapse".
+ if (mFloatingMaskView == null && isPrivateSpaceItem(currentItem)) {
+ RecyclerView.ViewHolder viewHolder =
+ allAppsRecyclerView.findViewHolderForAdapterPosition(i);
+ if (viewHolder != null) {
+ viewHolder.itemView.setVisibility(GONE);
+ currentItem.decorationInfo = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Upon expanding, only scroll to the item position in the adapter that allows the header to be
+ * visible.
+ */
+ public int scrollForHeaderToBeVisibleInContainer(
+ AllAppsRecyclerView allAppsRecyclerView,
+ List<BaseAllAppsAdapter.AdapterItem> appListAdapterItems,
+ int psHeaderHeight,
+ int allAppsCellHeight) {
+ int rowToExpandToWithRespectToHeader = -1;
+ int itemToScrollTo = -1;
+ // Looks for the item in the app list to scroll to so that the header is visible.
+ for (int i = 0; i < appListAdapterItems.size(); i++) {
+ BaseAllAppsAdapter.AdapterItem currentItem = appListAdapterItems.get(i);
+ if (currentItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
+ itemToScrollTo = i;
+ continue;
+ }
+ if (itemToScrollTo != -1) {
+ itemToScrollTo = i;
+ if (rowToExpandToWithRespectToHeader == -1) {
+ rowToExpandToWithRespectToHeader = currentItem.rowIndex;
+ }
+ // If there are no tabs, decrease the row to scroll to by 1 since the header
+ // may be cut off slightly.
+ int rowToScrollTo =
+ (int) Math.floor((double) (mAllApps.getHeight() - psHeaderHeight
+ - mAllApps.getHeaderProtectionHeight()) / allAppsCellHeight)
+ - (mAllApps.isUsingTabs() ? 0 : 1);
+ int currentRowDistance = currentItem.rowIndex - rowToExpandToWithRespectToHeader;
+ // rowToScrollTo - 1 since the item to scroll to is 0 indexed.
+ if (currentRowDistance == rowToScrollTo - 1) {
+ break;
+ }
+ }
+ }
+ if (itemToScrollTo != -1) {
+ // Note: SmoothScroller is meant to be used once.
+ RecyclerView.SmoothScroller smoothScroller =
+ new LinearSmoothScroller(mAllApps.getContext()) {
+ @Override protected int getVerticalSnapPreference() {
+ return LinearSmoothScroller.SNAP_TO_ANY;
+ }
+ };
+ smoothScroller.setTargetPosition(itemToScrollTo);
+ RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
+ if (layoutManager != null) {
+ startAnimationScroll(allAppsRecyclerView, layoutManager, smoothScroller);
+ }
+ }
+ return itemToScrollTo;
+ }
+
+ /**
+ * Scrolls up to the private space header and animates the collapsing of the text.
+ */
+ private ValueAnimator animateCollapseAnimation() {
+ float from = 1;
+ float to = 0;
+ RecyclerViewFastScroller scrollBar = mAllApps.getActiveRecyclerView().getScrollbar();
+ ValueAnimator collapseAnim = ValueAnimator.ofFloat(from, to);
+ collapseAnim.setDuration(EXPAND_COLLAPSE_DURATION);
+ collapseAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (scrollBar != null) {
+ scrollBar.setVisibility(INVISIBLE);
+ }
+ // Scroll up to header.
+ collapse();
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (scrollBar != null) {
+ scrollBar.setThumbOffsetY(-1);
+ scrollBar.setVisibility(VISIBLE);
+ }
+ }
+ });
+ return collapseAnim;
+ }
+
+ private ValueAnimator animateAlphaOfIcons(boolean isExpanding) {
+ float from = isExpanding ? 0 : 1;
+ float to = isExpanding ? 1 : 0;
+ AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
+ List<BaseAllAppsAdapter.AdapterItem> allAppsAdapterItems =
+ mAllApps.getActiveRecyclerView().getApps().getAdapterItems();
+ ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to);
+ alphaAnim.setDuration(APP_OPACITY_DURATION)
+ .setStartDelay(isExpanding ? APP_OPACITY_DELAY : NO_DELAY);
+ alphaAnim.setInterpolator(Interpolators.LINEAR);
+ alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float newAlpha = (float) valueAnimator.getAnimatedValue();
+ for (int i = 0; i < allAppsAdapterItems.size(); i++) {
+ BaseAllAppsAdapter.AdapterItem currentItem = allAppsAdapterItems.get(i);
+ // When not hidden: Fade all PS items except header.
+ // When hidden: Fade all items.
+ if (isPrivateSpaceItem(currentItem) &&
+ (currentItem.viewType != VIEW_TYPE_PRIVATE_SPACE_HEADER
+ || isPrivateSpaceHidden())) {
+ RecyclerView.ViewHolder viewHolder =
+ allAppsRecyclerView.findViewHolderForAdapterPosition(i);
+ if (viewHolder != null) {
+ viewHolder.itemView.setAlpha(newAlpha);
+ }
+ }
+ }
+ }
+ });
+ return alphaAnim;
+ }
+
+ /**
+ * Using PropertySetter{@link PropertySetter}, we can update the view's attributes within an
+ * animation. At the moment, collapsing, setting alpha changes, and animating the text is done
+ * here.
+ */
+ private void updatePrivateStateAnimator(boolean expand) {
+ if (!Flags.enablePrivateSpace() || !Flags.privateSpaceAnimation()) {
+ return;
+ }
+ if (mPSHeader == null) {
+ mOnPSHeaderAdded = () -> updatePrivateStateAnimator(expand);
+ setAnimationRunning(false);
+ return;
+ }
+ attachFloatingMaskView(expand);
+ ViewGroup settingsAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
+ if (settingsAndLockGroup.getLayoutTransition() == null) {
+ // Set a new transition if the current ViewGroup does not already contain one as each
+ // transition should only happen once when applied.
+ enableLayoutTransition(settingsAndLockGroup);
+ }
+ settingsAndLockGroup.getLayoutTransition().setStartDelay(
+ LayoutTransition.CHANGING,
+ expand ? SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY : NO_DELAY);
+ PropertySetter headerSetter = new AnimatedPropertySetter();
+ headerSetter.add(updateSettingsGearAlpha(expand));
+ headerSetter.add(updateLockTextAlpha(expand));
+ AnimatorSet animatorSet = headerSetter.buildAnim();
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mStatsLogManager.logger().sendToInteractionJankMonitor(
+ expand
+ ? LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN
+ : LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN,
+ mAllApps.getActiveRecyclerView());
+ // Animate the collapsing of the text at the same time while updating lock button.
+ mPSHeader.findViewById(R.id.lock_text).setVisibility(expand ? VISIBLE : GONE);
+ setAnimationRunning(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ detachFloatingMaskView();
+ }
+ });
+ animatorSet.addListener(forEndCallback(() -> {
+ mIsStateTransitioning = false;
+ setAnimationRunning(false);
+ getMainRecyclerView().setChildAttachedConsumer(child -> child.setAlpha(1));
+ mStatsLogManager.logger().sendToInteractionJankMonitor(
+ expand
+ ? LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END
+ : LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END,
+ mAllApps.getActiveRecyclerView());
+ if (!expand) {
+ mAllApps.mAH.get(MAIN).mRecyclerView.removeItemDecoration(
+ mPrivateAppsSectionDecorator);
+ // Call onAppsUpdated() because it may be canceled when this animation occurs.
+ mAllApps.getPersonalAppList().onAppsUpdated();
+ if (isPrivateSpaceHidden()) {
+ // TODO (b/325455879): Figure out if we can avoid this.
+ getMainRecyclerView().getAdapter().notifyDataSetChanged();
+ }
+ }
+ }));
+ if (expand) {
+ animatorSet.playTogether(animateAlphaOfIcons(true),
+ translateFloatingMaskView(false));
+ } else {
+ if (isPrivateSpaceHidden()) {
+ animatorSet.playSequentially(animateAlphaOfIcons(false),
+ animateAlphaOfPrivateSpaceContainer(),
+ animateCollapseAnimation());
+ } else {
+ animatorSet.playSequentially(translateFloatingMaskView(true),
+ animateAlphaOfIcons(false),
+ animateCollapseAnimation());
+ }
+ }
+ animatorSet.start();
+ }
+
+ /** Fades out the private space container (defined by its items' decorators). */
+ private ValueAnimator animateAlphaOfPrivateSpaceContainer() {
+ int from = 255; // 100% opacity.
+ int to = 0; // No opacity.
+ ValueAnimator alphaAnim = ObjectAnimator.ofInt(from, to);
+ AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
+ List<BaseAllAppsAdapter.AdapterItem> allAppsAdapterItems =
+ allAppsRecyclerView.getApps().getAdapterItems();
+ alphaAnim.setDuration(CONTAINER_OPACITY_DURATION);
+ alphaAnim.addUpdateListener(valueAnimator -> {
+ for (BaseAllAppsAdapter.AdapterItem currentItem : allAppsAdapterItems) {
+ if (isPrivateSpaceItem(currentItem)) {
+ currentItem.setDecorationFillAlpha((int) valueAnimator.getAnimatedValue());
+ }
+ }
+ // Invalidate the parent view, to redraw the decorations with changed alpha.
+ allAppsRecyclerView.invalidate();
+ });
+ return alphaAnim;
+ }
+
+ /** Fades out the private space container. */
+ private ValueAnimator translateFloatingMaskView(boolean animateIn) {
+ if (!Flags.privateSpaceAddFloatingMaskView() || mFloatingMaskView == null) {
+ return new ValueAnimator();
+ }
+ // Translate base on the height amount. Translates out on expand and in on collapse.
+ float floatingMaskViewHeight = getFloatingMaskViewHeight();
+ float from = animateIn ? floatingMaskViewHeight : 0;
+ float to = animateIn ? 0 : floatingMaskViewHeight;
+ ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to);
+ alphaAnim.setDuration(MASK_VIEW_DURATION);
+ alphaAnim.setStartDelay(MASK_VIEW_DELAY);
+ alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mFloatingMaskView.setTranslationY((float) valueAnimator.getAnimatedValue());
+ }
+ });
+ return alphaAnim;
+ }
+
+ /** Animates the layout changes when the text of the button becomes visible/gone. */
+ private void enableLayoutTransition(ViewGroup settingsAndLockGroup) {
+ LayoutTransition settingsAndLockTransition = new LayoutTransition();
+ settingsAndLockTransition.enableTransitionType(LayoutTransition.CHANGING);
+ settingsAndLockTransition.setDuration(EXPAND_COLLAPSE_DURATION);
+ settingsAndLockTransition.setInterpolator(LayoutTransition.CHANGING,
+ Interpolators.STANDARD);
+ settingsAndLockTransition.addTransitionListener(new LayoutTransition.TransitionListener() {
+ @Override
+ public void startTransition(LayoutTransition transition, ViewGroup viewGroup,
+ View view, int i) {
+ }
+ @Override
+ public void endTransition(LayoutTransition transition, ViewGroup viewGroup,
+ View view, int i) {
+ settingsAndLockGroup.setLayoutTransition(null);
+ mReadyToAnimate = false;
+ }
+ });
+ settingsAndLockGroup.setLayoutTransition(settingsAndLockTransition);
+ }
+
+ /** Change the settings gear alpha when expanded or collapsed. */
+ private ValueAnimator updateSettingsGearAlpha(boolean expand) {
+ if (mPSHeader == null) {
+ return new ValueAnimator();
+ }
+ float from = expand ? 0 : 1;
+ float to = expand ? 1 : 0;
+ ValueAnimator settingsAlphaAnim = ObjectAnimator.ofFloat(from, to);
+ settingsAlphaAnim.setDuration(SETTINGS_OPACITY_DURATION);
+ settingsAlphaAnim.setStartDelay(expand ? SETTINGS_OPACITY_DELAY : NO_DELAY);
+ settingsAlphaAnim.setInterpolator(Interpolators.LINEAR);
+ settingsAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mPSHeader.findViewById(R.id.ps_settings_button)
+ .setAlpha((float) valueAnimator.getAnimatedValue());
+ }
+ });
+ return settingsAlphaAnim;
+ }
+
+ private ValueAnimator updateLockTextAlpha(boolean expand) {
+ if (mPSHeader == null) {
+ return new ValueAnimator();
+ }
+ float from = expand ? 0 : 1;
+ float to = expand ? 1 : 0;
+ ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to);
+ alphaAnim.setDuration(expand ? TEXT_UNLOCK_OPACITY_DURATION : TEXT_LOCK_OPACITY_DURATION);
+ alphaAnim.setStartDelay(expand ? LOCK_TEXT_OPACITY_DELAY : NO_DELAY);
+ alphaAnim.setInterpolator(Interpolators.LINEAR);
+ alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mPSHeader.findViewById(R.id.lock_text).setAlpha(
+ (float) valueAnimator.getAnimatedValue());
+ }
+ });
+ return alphaAnim;
+ }
+
+ void expandPrivateSpace() {
+ // If we are on main adapter view, we apply the PS Container expansion animation and
+ // scroll down to load the entire container, making animation visible.
+ ActivityAllAppsContainerView<?>.AdapterHolder mainAdapterHolder = mAllApps.mAH.get(MAIN);
+ List<BaseAllAppsAdapter.AdapterItem> adapterItems =
+ mainAdapterHolder.mAppsList.getAdapterItems();
+ if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation()
+ && mAllApps.isPersonalTab()) {
+ // Animate the text and settings icon.
+ DeviceProfile deviceProfile =
+ ActivityContext.lookupContext(mAllApps.getContext()).getDeviceProfile();
+ scrollForHeaderToBeVisibleInContainer(mainAdapterHolder.mRecyclerView, adapterItems,
+ getPsHeaderHeight(), deviceProfile.allAppsCellHeightPx);
+ updatePrivateStateAnimator(true);
+ }
+ }
+
+ private void exitSearchAndExpand() {
+ mAllApps.updateHeaderScroll(0);
+ // Animate to A-Z with 0 time to reset the animation with proper state management.
+ mAllApps.animateToSearchState(false, 0);
+ MAIN_EXECUTOR.post(() -> {
+ mAllApps.mSearchUiManager.resetSearch();
+ mAllApps.switchToTab(ActivityAllAppsContainerView.AdapterHolder.MAIN);
+ expandPrivateSpace();
+ });
+ }
+
+ private void attachFloatingMaskView(boolean expand) {
+ if (!Flags.privateSpaceAddFloatingMaskView()) {
+ return;
+ }
+ mFloatingMaskView = (FloatingMaskView) mAllApps.getLayoutInflater().inflate(
+ R.layout.private_space_mask_view, mAllApps, false);
+ mAllApps.addView(mFloatingMaskView);
+ // Translate off the screen first if its collapsing so this header view isn't visible to
+ // user when animation starts.
+ if (!expand) {
+ mFloatingMaskView.setTranslationY(getFloatingMaskViewHeight());
+ }
+ mFloatingMaskView.setVisibility(VISIBLE);
+ }
+
+ private void detachFloatingMaskView() {
+ if (mFloatingMaskView != null) {
+ mAllApps.removeView(mFloatingMaskView);
+ }
+ mFloatingMaskView = null;
+ }
+
+ /** Starts the smooth scroll with the provided smoothScroller and add idle listener. */
+ private void startAnimationScroll(AllAppsRecyclerView allAppsRecyclerView,
+ RecyclerView.LayoutManager layoutManager, RecyclerView.SmoothScroller smoothScroller) {
+ mIsScrolling = true;
+ layoutManager.startSmoothScroll(smoothScroller);
+ allAppsRecyclerView.removeOnScrollListener(mOnIdleScrollListener);
+ allAppsRecyclerView.addOnScrollListener(mOnIdleScrollListener);
+ }
+
+ private float getFloatingMaskViewHeight() {
+ return mFloatingMaskViewCornerRadius + getMainRecyclerView().getPaddingBottom();
+ }
+
+ AllAppsRecyclerView getMainRecyclerView() {
+ return mAllApps.mAH.get(ActivityAllAppsContainerView.AdapterHolder.MAIN).mRecyclerView;
+ }
+
+ /** Returns if private space is readily available to be animated. */
+ boolean getReadyToAnimate() {
+ return mReadyToAnimate;
+ }
+
+ /** Returns when a smooth scroll is happening. */
+ boolean isScrolling() {
+ return mIsScrolling;
+ }
+
+ /**
+ * Returns when private space is in the process of transitioning. This is different from
+ * getAnimate() since mStateTransitioning checks from the time transitioning starts happening
+ * in reset() as oppose to when private space is animating. This should be used to ensure
+ * Private Space state during onBind().
+ */
+ boolean isStateTransitioning() {
+ return mIsStateTransitioning;
+ }
+
+ int getPsHeaderHeight() {
+ return mPsHeaderHeight;
+ }
+
+ boolean isPrivateSpaceItem(BaseAllAppsAdapter.AdapterItem item) {
+ return getItemInfoMatcher().test(item.itemInfo) || item.decorationInfo != null
+ || (item.itemInfo instanceof PrivateSpaceInstallAppButtonInfo);
}
}
diff --git a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
deleted file mode 100644
index 6067454..0000000
--- a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.allapps;
-
-import static android.view.View.GONE;
-import static android.view.View.INVISIBLE;
-import static android.view.View.VISIBLE;
-
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
-import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
-import static com.android.launcher3.allapps.PrivateProfileManager.STATE_DISABLED;
-import static com.android.launcher3.allapps.PrivateProfileManager.STATE_ENABLED;
-import static com.android.launcher3.allapps.PrivateProfileManager.STATE_TRANSITION;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_LOCK_TAP;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.LayoutTransition;
-import android.animation.ValueAnimator;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import androidx.recyclerview.widget.LinearSmoothScroller;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.app.animation.Interpolators;
-import com.android.launcher3.Flags;
-import com.android.launcher3.R;
-import com.android.launcher3.allapps.UserProfileManager.UserProfileState;
-import com.android.launcher3.anim.AnimatedPropertySetter;
-import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.views.RecyclerViewFastScroller;
-
-import java.util.List;
-
-/**
- * Controller which returns views to be added to Private Space Header based upon
- * {@link UserProfileState}
- */
-public class PrivateSpaceHeaderViewController {
- private static final int EXPAND_SCROLL_DURATION = 2000;
- private static final int EXPAND_COLLAPSE_DURATION = 800;
- private static final int SETTINGS_OPACITY_DURATION = 160;
- private final ActivityAllAppsContainerView mAllApps;
- private final PrivateProfileManager mPrivateProfileManager;
-
- public PrivateSpaceHeaderViewController(ActivityAllAppsContainerView allApps,
- PrivateProfileManager privateProfileManager) {
- this.mAllApps = allApps;
- this.mPrivateProfileManager = privateProfileManager;
- }
-
- /** Add Private Space Header view elements based upon {@link UserProfileState} */
- public void addPrivateSpaceHeaderViewElements(RelativeLayout parent) {
- // Set the transition duration for the settings and lock button to animate.
- ViewGroup settingsAndLockGroup = parent.findViewById(R.id.settingsAndLockGroup);
- LayoutTransition settingsAndLockTransition = settingsAndLockGroup.getLayoutTransition();
- settingsAndLockTransition.enableTransitionType(LayoutTransition.CHANGING);
- settingsAndLockTransition.setDuration(EXPAND_COLLAPSE_DURATION);
-
- //Add quietMode image and action for lock/unlock button
- ViewGroup lockButton =
- parent.findViewById(R.id.ps_lock_unlock_button);
- assert lockButton != null;
- addLockButton(parent, lockButton);
-
- //Trigger lock/unlock action from header.
- addHeaderOnClickListener(parent);
-
- //Add image and action for private space settings button
- ImageButton settingsButton = parent.findViewById(R.id.ps_settings_button);
- assert settingsButton != null;
- addPrivateSpaceSettingsButton(settingsButton);
-
- //Add image for private space transitioning view
- ImageView transitionView = parent.findViewById(R.id.ps_transition_image);
- assert transitionView != null;
- addTransitionImage(transitionView);
- }
-
- /**
- * Adds the quietModeButton and attach onClickListener for the header to animate different
- * states when clicked.
- */
- private void addLockButton(ViewGroup psHeader, ViewGroup lockButton) {
- TextView lockText = lockButton.findViewById(R.id.lock_text);
- switch (mPrivateProfileManager.getCurrentState()) {
- case STATE_ENABLED -> {
- lockText.setVisibility(VISIBLE);
- lockButton.setVisibility(VISIBLE);
- lockButton.setOnClickListener(view -> lockAction(psHeader));
- }
- case STATE_DISABLED -> {
- lockText.setVisibility(GONE);
- lockButton.setVisibility(VISIBLE);
- lockButton.setOnClickListener(view -> unlockAction(psHeader));
- }
- default -> lockButton.setVisibility(GONE);
- }
- }
-
- private void addHeaderOnClickListener(RelativeLayout header) {
- if (mPrivateProfileManager.getCurrentState() == STATE_DISABLED) {
- header.setOnClickListener(view -> unlockAction(header));
- } else {
- header.setOnClickListener(null);
- }
- }
-
- private void unlockAction(ViewGroup psHeader) {
- mPrivateProfileManager.logEvents(LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP);
- mPrivateProfileManager.unlockPrivateProfile((() -> onPrivateProfileUnlocked(psHeader)));
- }
-
- private void lockAction(ViewGroup psHeader) {
- mPrivateProfileManager.logEvents(LAUNCHER_PRIVATE_SPACE_LOCK_TAP);
- if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation()) {
- updatePrivateStateAnimator(false, psHeader);
- } else {
- mPrivateProfileManager.lockPrivateProfile();
- }
- }
-
- private void addPrivateSpaceSettingsButton(ImageButton settingsButton) {
- if (mPrivateProfileManager.getCurrentState() == STATE_ENABLED
- && mPrivateProfileManager.isPrivateSpaceSettingsAvailable()) {
- settingsButton.setVisibility(VISIBLE);
- settingsButton.setAlpha(1f);
- settingsButton.setOnClickListener(
- view -> {
- mPrivateProfileManager.logEvents(LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP);
- mPrivateProfileManager.openPrivateSpaceSettings();
- });
- } else {
- settingsButton.setVisibility(GONE);
- }
- }
-
- private void addTransitionImage(ImageView transitionImage) {
- if (mPrivateProfileManager.getCurrentState() == STATE_TRANSITION) {
- transitionImage.setVisibility(VISIBLE);
- } else {
- transitionImage.setVisibility(GONE);
- }
- }
-
- private void onPrivateProfileUnlocked(ViewGroup header) {
- // If we are on main adapter view, we apply the PS Container expansion animation and
- // then scroll down to load the entire container, making animation visible.
- ActivityAllAppsContainerView<?>.AdapterHolder mainAdapterHolder =
- (ActivityAllAppsContainerView<?>.AdapterHolder) mAllApps.mAH.get(MAIN);
- if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation()
- && mAllApps.getActiveRecyclerView() == mainAdapterHolder.mRecyclerView) {
- // Animate the text and settings icon.
- updatePrivateStateAnimator(true, header);
- mAllApps.getActiveRecyclerView().scrollToBottomWithMotion(EXPAND_SCROLL_DURATION);
- }
- }
-
- /** Finds the private space header to scroll to and set the private space icons to GONE. */
- private void collapse() {
- AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
- for (int i = allAppsRecyclerView.getChildCount() - 1; i > 0; i--) {
- int adapterPosition = allAppsRecyclerView.getChildAdapterPosition(
- allAppsRecyclerView.getChildAt(i));
- List<BaseAllAppsAdapter.AdapterItem> allAppsAdapters = allAppsRecyclerView.getApps()
- .getAdapterItems();
- if (adapterPosition < 0 || adapterPosition >= allAppsAdapters.size()) {
- continue;
- }
- // Scroll to the private space header.
- if (allAppsAdapters.get(adapterPosition).viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
- // Note: SmoothScroller is meant to be used once.
- RecyclerView.SmoothScroller smoothScroller =
- new LinearSmoothScroller(mAllApps.getContext()) {
- @Override protected int getVerticalSnapPreference() {
- return LinearSmoothScroller.SNAP_TO_END;
- }
- };
- smoothScroller.setTargetPosition(adapterPosition);
- RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
- if (layoutManager != null) {
- layoutManager.startSmoothScroll(smoothScroller);
- }
- break;
- }
- // Make the private space apps gone to "collapse".
- if (allAppsAdapters.get(adapterPosition).decorationInfo != null) {
- allAppsRecyclerView.getChildAt(i).setVisibility(GONE);
- }
- }
- }
-
- PrivateProfileManager getPrivateProfileManager() {
- return mPrivateProfileManager;
- }
-
- /**
- * Scrolls up to the private space header and animates the collapsing of the text.
- */
- private ValueAnimator animateCollapseAnimation(ViewGroup lockButton) {
- float from = 1;
- float to = 0;
- RecyclerViewFastScroller scrollBar = mAllApps.getActiveRecyclerView().getScrollbar();
- ValueAnimator collapseAnim = ValueAnimator.ofFloat(from, to);
- collapseAnim.setDuration(EXPAND_COLLAPSE_DURATION);
- collapseAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- if (scrollBar != null) {
- scrollBar.setVisibility(INVISIBLE);
- }
- // scroll up
- collapse();
- // Animate the collapsing of the text.
- lockButton.findViewById(R.id.lock_text).setVisibility(GONE);
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- if (scrollBar != null) {
- scrollBar.setThumbOffsetY(-1);
- scrollBar.setVisibility(VISIBLE);
- }
- mPrivateProfileManager.lockPrivateProfile();
- }
- });
- return collapseAnim;
- }
-
- /**
- * Using PropertySetter{@link PropertySetter}, we can update the view's attributes within an
- * animation. At the moment, collapsing, setting alpha changes, and animating the text is done
- * here.
- */
- private void updatePrivateStateAnimator(boolean expand, ViewGroup psHeader) {
- PropertySetter setter = new AnimatedPropertySetter();
- ViewGroup lockButton = psHeader.findViewById(R.id.ps_lock_unlock_button);
- ImageButton settingsButton = psHeader.findViewById(R.id.ps_settings_button);
- updateSettingsGearAlpha(settingsButton, expand, setter);
- AnimatorSet animatorSet = setter.buildAnim();
- animatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- // Animate the collapsing of the text at the same time while updating lock button.
- lockButton.findViewById(R.id.lock_text).setVisibility(expand ? VISIBLE : GONE);
- }
- });
- // Play the collapsing together of the stateAnimator to avoid being unable to scroll to the
- // header. Otherwise the smooth scrolling will scroll higher when played with the state
- // animator.
- if (!expand) {
- animatorSet.playTogether(animateCollapseAnimation(lockButton));
- }
- animatorSet.setDuration(EXPAND_COLLAPSE_DURATION);
- animatorSet.start();
- }
-
- /** Change the settings gear alpha when expanded or collapsed. */
- private void updateSettingsGearAlpha(ImageButton settingsButton, boolean expand,
- PropertySetter setter) {
- float toAlpha = expand ? 1 : 0;
- setter.setFloat(settingsButton, VIEW_ALPHA, toAlpha, Interpolators.LINEAR)
- .setDuration(SETTINGS_OPACITY_DURATION).setStartDelay(0);
- }
-}
diff --git a/src/com/android/launcher3/allapps/PrivateSpaceSettingsButton.java b/src/com/android/launcher3/allapps/PrivateSpaceSettingsButton.java
new file mode 100644
index 0000000..43e42ff
--- /dev/null
+++ b/src/com/android/launcher3/allapps/PrivateSpaceSettingsButton.java
@@ -0,0 +1,81 @@
+/*
+ * 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.allapps;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PRIVATESPACE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.util.ApiWrapper;
+import com.android.launcher3.views.ActivityContext;
+
+public class PrivateSpaceSettingsButton extends ImageButton implements View.OnClickListener {
+
+ private final ActivityContext mActivityContext;
+ private final StatsLogManager mStatsLogManager;
+ private final Intent mPrivateSpaceSettingsIntent;
+
+ public PrivateSpaceSettingsButton(Context context) {
+ this(context, null, 0);
+ }
+
+ public PrivateSpaceSettingsButton(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PrivateSpaceSettingsButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mActivityContext = ActivityContext.lookupContext(context);
+ mStatsLogManager = mActivityContext.getStatsLogManager();
+ mPrivateSpaceSettingsIntent =
+ ApiWrapper.INSTANCE.get(context).getPrivateSpaceSettingsIntent();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View view) {
+ mStatsLogManager.logger().log(LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP);
+ AppInfo privateSpaceSettingsItemInfo = createPrivateSpaceSettingsAppInfo();
+ view.setTag(privateSpaceSettingsItemInfo);
+ mActivityContext.startActivitySafely(
+ view,
+ mPrivateSpaceSettingsIntent,
+ privateSpaceSettingsItemInfo);
+ }
+
+ AppInfo createPrivateSpaceSettingsAppInfo() {
+ AppInfo itemInfo = new AppInfo();
+ itemInfo.id = CONTAINER_PRIVATESPACE;
+ if (mPrivateSpaceSettingsIntent != null) {
+ itemInfo.componentName = mPrivateSpaceSettingsIntent.getComponent();
+ }
+ itemInfo.container = CONTAINER_PRIVATESPACE;
+ return itemInfo;
+ }
+}
diff --git a/src/com/android/launcher3/allapps/SectionDecorationHandler.java b/src/com/android/launcher3/allapps/SectionDecorationHandler.java
index f79b82c..ac9b146 100644
--- a/src/com/android/launcher3/allapps/SectionDecorationHandler.java
+++ b/src/com/android/launcher3/allapps/SectionDecorationHandler.java
@@ -176,13 +176,10 @@
/**
* Expands decoration bounds to include child {@link PrivateAppsSectionDecorator}
*/
- public void addChild(SectionDecorationHandler child, View view, boolean applyBackground) {
+ public void addChild(SectionDecorationHandler child, View view) {
int scaledHeight = (int) (view.getHeight() * view.getScaleY());
mBounds.union(view.getLeft(), view.getY(),
view.getRight(), view.getY() + scaledHeight);
- if (applyBackground) {
- applyBackground(view, mContext, null, false);
- }
mIsBottomRound |= child.mIsBottomRound;
mIsBottomLeftRound |= child.mIsBottomLeftRound;
mIsBottomRightRound |= child.mIsBottomRightRound;
diff --git a/src/com/android/launcher3/allapps/UserProfileManager.java b/src/com/android/launcher3/allapps/UserProfileManager.java
index 6a1f37a..93b6b29 100644
--- a/src/com/android/launcher3/allapps/UserProfileManager.java
+++ b/src/com/android/launcher3/allapps/UserProfileManager.java
@@ -40,11 +40,13 @@
* {@link PrivateProfileManager} which manages private profile state.
*/
public abstract class UserProfileManager {
+ public static final int STATE_UNKNOWN = 0;
public static final int STATE_ENABLED = 1;
public static final int STATE_DISABLED = 2;
public static final int STATE_TRANSITION = 3;
@IntDef(value = {
+ STATE_UNKNOWN,
STATE_ENABLED,
STATE_DISABLED,
STATE_TRANSITION
@@ -52,13 +54,12 @@
@Retention(RetentionPolicy.SOURCE)
public @interface UserProfileState { }
+ protected final StatsLogManager mStatsLogManager;
+ protected final UserManager mUserManager;
+ protected final UserCache mUserCache;
+
@UserProfileState
private int mCurrentState;
-
- private final UserManager mUserManager;
- private final StatsLogManager mStatsLogManager;
- private final UserCache mUserCache;
-
protected UserProfileManager(UserManager userManager,
StatsLogManager statsLogManager,
UserCache userCache) {
diff --git a/src/com/android/launcher3/allapps/WorkEduCard.java b/src/com/android/launcher3/allapps/WorkEduCard.java
index 1059097..5fe2f0c 100644
--- a/src/com/android/launcher3/allapps/WorkEduCard.java
+++ b/src/com/android/launcher3/allapps/WorkEduCard.java
@@ -103,6 +103,8 @@
AllAppsRecyclerView rv = mActivityContext.getAppsView().mAH.get(
ActivityAllAppsContainerView.AdapterHolder.WORK).mRecyclerView;
rv.getApps().getAdapterItems().remove(mPosition);
+ // Remove the educard fast scroll section.
+ rv.getApps().getFastScrollerSections().remove(0);
rv.getAdapter().notifyItemRemoved(mPosition);
}
}
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index eb7d429..6049574 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -15,14 +15,10 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.getTabWidth;
-
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.view.View;
import android.view.WindowInsets;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -35,7 +31,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
-import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.views.ActivityContext;
/**
@@ -53,12 +48,11 @@
private final Rect mImeInsets = new Rect();
private int mFlags;
private final ActivityContext mActivityContext;
+ private final Context mContext;
// Threshold when user scrolls up/down to determine when should button extend/collapse
private final int mScrollThreshold;
- private ImageView mIcon;
private TextView mTextView;
- private final StatsLogManager mStatsLogManager;
public WorkModeSwitch(@NonNull Context context) {
@@ -71,16 +65,15 @@
public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ mContext = context;
mScrollThreshold = Utilities.dpToPx(SCROLL_THRESHOLD_DP);
mActivityContext = ActivityContext.lookupContext(getContext());
- mStatsLogManager = mActivityContext.getStatsLogManager();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mIcon = findViewById(R.id.work_icon);
mTextView = findViewById(R.id.pause_text);
setSelected(true);
KeyboardInsetAnimationCallback keyboardInsetAnimationCallback =
@@ -114,13 +107,8 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- View parent = (View) getParent();
boolean isRtl = Utilities.isRtl(getResources());
- Rect allAppsPadding = mActivityContext.getDeviceProfile().allAppsPadding;
- int size = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight()
- - (allAppsPadding.left + allAppsPadding.right);
- int tabWidth = getTabWidth(getContext(), size);
- int shift = (size - tabWidth) / 2 + (isRtl ? allAppsPadding.left : allAppsPadding.right);
+ int shift = mActivityContext.getDeviceProfile().getAllAppsIconStartMargin(mContext);
setTranslationX(isRtl ? shift : -shift);
}
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index a54e52c..96998a3 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -73,7 +73,7 @@
* Posts quite mode enable/disable call for work profile user
*/
public void setWorkProfileEnabled(boolean enabled) {
- setCurrentState(STATE_TRANSITION);
+ updateCurrentState(STATE_TRANSITION);
setQuietMode(!enabled);
}
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index f9d047b..ec45415 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -155,6 +155,7 @@
public void reset() {
mCallback.clearSearchResult();
mInput.reset();
+ mInput.clearFocus();
mQuery = null;
mInput.removeOnFocusChangeListener(this);
}
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index ab47097..8121d2a 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -22,13 +22,9 @@
import android.os.Handler;
import androidx.annotation.AnyThread;
-import androidx.annotation.NonNull;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
-import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.search.SearchAlgorithm;
import com.android.launcher3.search.SearchCallback;
@@ -67,16 +63,12 @@
@Override
public void doSearch(String query, SearchCallback<AdapterItem> callback) {
- mAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- ArrayList<AdapterItem> result = getTitleMatchResult(apps.data, query);
- if (mAddNoResultsMessage && result.isEmpty()) {
- result.add(getEmptyMessageAdapterItem(query));
- }
- mResultHandler.post(() -> callback.onSearchResult(query, result));
+ mAppState.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ ArrayList<AdapterItem> result = getTitleMatchResult(apps.data, query);
+ if (mAddNoResultsMessage && result.isEmpty()) {
+ result.add(getEmptyMessageAdapterItem(query));
}
+ mResultHandler.post(() -> callback.onSearchResult(query, result));
});
}
diff --git a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
index 64fd237..4a8c96b 100644
--- a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
@@ -19,8 +19,6 @@
import android.view.View;
import android.view.ViewGroup;
-import androidx.recyclerview.widget.RecyclerView;
-
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.model.data.ItemInfo;
@@ -30,13 +28,10 @@
* Provides views for local search results.
*/
public class DefaultSearchAdapterProvider extends SearchAdapterProvider<ActivityContext> {
-
- private final RecyclerView.ItemDecoration mDecoration;
private View mHighlightedView;
public DefaultSearchAdapterProvider(ActivityContext launcher) {
super(launcher);
- mDecoration = new RecyclerView.ItemDecoration() { };
}
@Override
@@ -74,11 +69,6 @@
}
@Override
- public RecyclerView.ItemDecoration getDecorator() {
- return mDecoration;
- }
-
- @Override
public void clearHighlightedItem() {
mHighlightedView = null;
}
diff --git a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
index 15756f5..82c9c90 100644
--- a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
@@ -20,8 +20,6 @@
import android.view.View;
import android.view.ViewGroup;
-import androidx.recyclerview.widget.RecyclerView;
-
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.views.ActivityContext;
@@ -50,11 +48,6 @@
public abstract View getHighlightedItem();
/**
- * Returns the item decorator.
- */
- public abstract RecyclerView.ItemDecoration getDecorator();
-
- /**
* Clear the highlighted view.
*/
public abstract void clearHighlightedItem();
diff --git a/src/com/android/launcher3/anim/AnimatedFloat.java b/src/com/android/launcher3/anim/AnimatedFloat.java
index b414ab6..4441164 100644
--- a/src/com/android/launcher3/anim/AnimatedFloat.java
+++ b/src/com/android/launcher3/anim/AnimatedFloat.java
@@ -20,6 +20,8 @@
import android.animation.ObjectAnimator;
import android.util.FloatProperty;
+import java.util.function.Consumer;
+
/**
* A mutable float which allows animating the value
*/
@@ -38,9 +40,9 @@
}
};
- private static final Runnable NO_OP = () -> { };
+ private static final Consumer<Float> NO_OP = t -> { };
- private final Runnable mUpdateCallback;
+ private final Consumer<Float> mUpdateCallback;
private ObjectAnimator mValueAnimator;
// Only non-null when an animation is playing to this value.
private Float mEndValue;
@@ -52,6 +54,10 @@
}
public AnimatedFloat(Runnable updateCallback) {
+ this(v -> updateCallback.run());
+ }
+
+ public AnimatedFloat(Consumer<Float> updateCallback) {
mUpdateCallback = updateCallback;
}
@@ -60,6 +66,11 @@
value = initialValue;
}
+ public AnimatedFloat(Consumer<Float> updateCallback, float initialValue) {
+ this(updateCallback);
+ value = initialValue;
+ }
+
/**
* Returns an animation from the current value to the given value.
*/
@@ -99,7 +110,7 @@
public void updateValue(float v) {
if (Float.compare(v, value) != 0) {
value = v;
- mUpdateCallback.run();
+ mUpdateCallback.accept(value);
}
}
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index e58890f..47a2bdd 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -59,6 +59,13 @@
add(anim, springProperty);
}
+ /**
+ * Utility method to sent an interpolator on an animation and add it to the list
+ */
+ public void add(Animator anim, TimeInterpolator interpolator) {
+ add(anim, interpolator, SpringProperty.DEFAULT);
+ }
+
@Override
public void add(Animator anim) {
add(anim, SpringProperty.DEFAULT);
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 48d0fbd..32445ec 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -16,13 +16,13 @@
package com.android.launcher3.apppairs;
+import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
+
import android.content.Context;
-import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
-import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -30,15 +30,18 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
-import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.AppPairInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.views.ActivityContext;
-import java.util.Collections;
import java.util.Comparator;
import java.util.function.Predicate;
@@ -51,17 +54,15 @@
public class AppPairIcon extends FrameLayout implements DraggableView, Reorderable {
private static final String TAG = "AppPairIcon";
- /**
- * Indicates that the app pair is currently launchable on the current screen.
- */
- private boolean mIsLaunchableAtScreenSize = true;
-
// A view that holds the app pair icon graphic.
private AppPairIconGraphic mIconGraphic;
// A view that holds the app pair's title.
private BubbleTextView mAppPairName;
// The underlying ItemInfo that stores info about the app pair members, etc.
- private FolderInfo mInfo;
+ private AppPairInfo mInfo;
+ // The containing element that holds this icon: workspace, taskbar, folder, etc. Affects certain
+ // aspects of how the icon is drawn.
+ private int mContainer;
// Required for Reorderable -- handles translation and bouncing movements
private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
@@ -79,54 +80,79 @@
* Builds an AppPairIcon to be added to the Launcher.
*/
public static AppPairIcon inflateIcon(int resId, ActivityContext activity,
- @Nullable ViewGroup group, FolderInfo appPairInfo) {
+ @Nullable ViewGroup group, AppPairInfo appPairInfo, int container) {
DeviceProfile grid = activity.getDeviceProfile();
LayoutInflater inflater = (group != null)
? LayoutInflater.from(group.getContext())
: activity.getLayoutInflater();
AppPairIcon icon = (AppPairIcon) inflater.inflate(resId, group, false);
- // Sort contents, so that left-hand app comes first
- Collections.sort(appPairInfo.contents, Comparator.comparingInt(a -> a.rank));
+ if (Flags.enableFocusOutline() && activity instanceof Launcher) {
+ icon.setOnFocusChangeListener(((Launcher) activity).getFocusHandler());
+ icon.setDefaultFocusHighlightEnabled(false);
+ }
- icon.setClipToPadding(false);
+ // Sort contents, so that left-hand app comes first
+ appPairInfo.getContents().sort(Comparator.comparingInt(a -> a.rank));
+
icon.setTag(appPairInfo);
icon.setOnClickListener(activity.getItemOnClickListener());
icon.mInfo = appPairInfo;
-
- if (icon.mInfo.contents.size() != 2) {
- Log.wtf(TAG, "AppPair contents not 2, size: " + icon.mInfo.contents.size());
- return icon;
- }
-
- icon.checkScreenSize();
+ icon.mContainer = container;
// Set up icon drawable area
icon.mIconGraphic = icon.findViewById(R.id.app_pair_icon_graphic);
- icon.mIconGraphic.init(activity.getDeviceProfile(), icon);
+ icon.mIconGraphic.init(icon, container);
// Set up app pair title
icon.mAppPairName = icon.findViewById(R.id.app_pair_icon_name);
- icon.mAppPairName.setCompoundDrawablePadding(0);
FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams) icon.mAppPairName.getLayoutParams();
- lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
- icon.mAppPairName.setText(appPairInfo.title);
+ // Shift the title text down to leave room for the icon graphic. Since the icon graphic is
+ // a separate element (and not set as a CompoundDrawable on the BubbleTextView), we need to
+ // shift the text down manually.
+ lp.topMargin = container == DISPLAY_FOLDER
+ ? grid.folderChildIconSizePx + grid.folderChildDrawablePaddingPx
+ : grid.iconSizePx + grid.iconDrawablePaddingPx;
+ // For some reason, app icons have setIncludeFontPadding(false) inside folders, so we set it
+ // here to match that.
+ icon.mAppPairName.setIncludeFontPadding(container != DISPLAY_FOLDER);
+ // Set title text and accessibility title text.
+ icon.updateTitleAndA11yTitle();
- // Set up accessibility
- icon.setContentDescription(icon.getAccessibilityTitle(appPairInfo));
icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
return icon;
}
/**
- * Returns a formatted accessibility title for app pairs.
+ * Updates the title and a11y title of the app pair. Called on creation and when packages
+ * change, to reflect app name changes or user language changes.
*/
- public String getAccessibilityTitle(FolderInfo appPairInfo) {
- CharSequence app1 = appPairInfo.contents.get(0).title;
- CharSequence app2 = appPairInfo.contents.get(1).title;
- return getContext().getString(R.string.app_pair_name_format, app1, app2);
+ public void updateTitleAndA11yTitle() {
+ updateTitleAndTextView();
+ updateAccessibilityTitle();
+ }
+
+ /**
+ * Updates AppPairInfo with a formatted app pair title, and sets it on the BubbleTextView.
+ */
+ public void updateTitleAndTextView() {
+ CharSequence newTitle = getInfo().generateTitle(getContext());
+ mAppPairName.setText(newTitle);
+ }
+
+ /**
+ * Updates the accessibility title with a formatted string template.
+ */
+ public void updateAccessibilityTitle() {
+ CharSequence app1 = getInfo().getFirstApp().title;
+ CharSequence app2 = getInfo().getSecondApp().title;
+ String a11yTitle = getContext().getString(R.string.app_pair_name_format, app1, app2);
+ setContentDescription(
+ getInfo().shouldReportDisabled(getContext())
+ ? getContext().getString(R.string.disabled_app_label, a11yTitle)
+ : a11yTitle);
}
// Required for DraggableView
@@ -170,47 +196,58 @@
return mScaleForReorderBounce;
}
- public FolderInfo getInfo() {
+ public AppPairInfo getInfo() {
return mInfo;
}
- public View getIconDrawableArea() {
+ public BubbleTextView getTitleTextView() {
+ return mAppPairName;
+ }
+
+ public AppPairIconGraphic getIconDrawableArea() {
return mIconGraphic;
}
- public boolean isLaunchableAtScreenSize() {
- return mIsLaunchableAtScreenSize;
+ public int getContainer() {
+ return mContainer;
}
/**
- * Checks if the app pair is launchable in the current device configuration.
- *
- * App pairs can be "disabled" in two ways:
- * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is paused
- * by the user or can't be launched).
- * 2) This specific instance of an app pair can't be launched due to screen size requirements.
- *
- * This method checks and updates #2. Both #1 and #2 are checked when app pairs are drawn
- * {@link AppPairIconGraphic#dispatchDraw(Canvas)} or clicked on
- * {@link com.android.launcher3.touch.ItemClickHandler#onClickAppPairIcon(View)}
+ * Ensures that both app icons in the pair are loaded in high resolution.
*/
- public void checkScreenSize() {
- DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
- // If user is on a small screen, we can't launch if either of the apps is non-resizeable
- mIsLaunchableAtScreenSize =
- dp.isTablet || getInfo().contents.stream().noneMatch(
- wii -> wii.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE));
+ public void verifyHighRes() {
+ IconCache iconCache = LauncherAppState.getInstance(getContext()).getIconCache();
+ getInfo().fetchHiResIconsIfNeeded(iconCache);
}
/**
* Called when WorkspaceItemInfos get updated, and the app pair icon may need to be redrawn.
*/
- public void maybeRedrawForWorkspaceUpdate(Predicate<WorkspaceItemInfo> itemCheck) {
+ public void maybeRedrawForWorkspaceUpdate(Predicate<ItemInfo> itemCheck) {
// If either of the app pair icons return true on the predicate (i.e. in the list of
// updated apps), redraw the icon graphic (icon background and both icons).
- if (getInfo().contents.stream().anyMatch(itemCheck)) {
- checkScreenSize();
- mIconGraphic.invalidate();
+ if (getInfo().anyMatch(itemCheck)) {
+ updateTitleAndA11yTitle();
+ mIconGraphic.redraw();
}
}
+
+ /**
+ * Inside folders, icons are vertically centered in their rows. See
+ * {@link BubbleTextView} for comparison.
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mContainer == DISPLAY_FOLDER) {
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ ActivityContext activity = ActivityContext.lookupContext(getContext());
+ Paint.FontMetrics fm = mAppPairName.getPaint().getFontMetrics();
+ int cellHeightPx = activity.getDeviceProfile().folderChildIconSizePx
+ + activity.getDeviceProfile().folderChildDrawablePaddingPx
+ + (int) Math.ceil(fm.bottom - fm.top);
+ setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(),
+ getPaddingBottom());
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconBackground.java b/src/com/android/launcher3/apppairs/AppPairIconBackground.java
deleted file mode 100644
index b5011f1..0000000
--- a/src/com/android/launcher3/apppairs/AppPairIconBackground.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.apppairs;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-
-import com.android.launcher3.R;
-
-/**
- * A Drawable for the background behind the twin app icons (looks like two rectangles).
- */
-class AppPairIconBackground extends Drawable {
- // The underlying view that we are drawing this background on.
- private final AppPairIconGraphic icon;
- private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- /**
- * Null values to use with
- * {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)}, since there
- * doesn't seem to be any other API for drawing rectangles with 4 different corner radii.
- */
- private static final RectF EMPTY_RECT = new RectF();
- private static final float[] ARRAY_OF_ZEROES = new float[8];
-
- AppPairIconBackground(Context context, AppPairIconGraphic iconGraphic) {
- icon = iconGraphic;
- // Set up background paint color
- TypedArray ta = context.getTheme().obtainStyledAttributes(R.styleable.FolderIconPreview);
- mBackgroundPaint.setStyle(Paint.Style.FILL);
- mBackgroundPaint.setColor(
- ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0));
- ta.recycle();
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (icon.isLeftRightSplit()) {
- drawLeftRightSplit(canvas);
- } else {
- drawTopBottomSplit(canvas);
- }
- }
-
- /**
- * When device is in landscape, we draw the rectangles with a left-right split.
- */
- private void drawLeftRightSplit(Canvas canvas) {
- // Get the bounds where we will draw the background image
- int width = getBounds().width();
- int height = getBounds().height();
-
- // The left half of the background image, excluding center channel
- RectF leftSide = new RectF(
- 0,
- 0,
- (width / 2f) - (icon.getCenterChannelSize() / 2f),
- height
- );
- // The right half of the background image, excluding center channel
- RectF rightSide = new RectF(
- (width / 2f) + (icon.getCenterChannelSize() / 2f),
- 0,
- width,
- height
- );
-
- drawCustomRoundedRect(canvas, leftSide, new float[]{
- icon.getBigRadius(), icon.getBigRadius(),
- icon.getSmallRadius(), icon.getSmallRadius(),
- icon.getSmallRadius(), icon.getSmallRadius(),
- icon.getBigRadius(), icon.getBigRadius()});
- drawCustomRoundedRect(canvas, rightSide, new float[]{
- icon.getSmallRadius(), icon.getSmallRadius(),
- icon.getBigRadius(), icon.getBigRadius(),
- icon.getBigRadius(), icon.getBigRadius(),
- icon.getSmallRadius(), icon.getSmallRadius()});
- }
-
- /**
- * When device is in portrait, we draw the rectangles with a top-bottom split.
- */
- private void drawTopBottomSplit(Canvas canvas) {
- // Get the bounds where we will draw the background image
- int width = getBounds().width();
- int height = getBounds().height();
-
- // The top half of the background image, excluding center channel
- RectF topSide = new RectF(
- 0,
- 0,
- width,
- (height / 2f) - (icon.getCenterChannelSize() / 2f)
- );
- // The bottom half of the background image, excluding center channel
- RectF bottomSide = new RectF(
- 0,
- (height / 2f) + (icon.getCenterChannelSize() / 2f),
- width,
- height
- );
-
- drawCustomRoundedRect(canvas, topSide, new float[]{
- icon.getBigRadius(), icon.getBigRadius(),
- icon.getBigRadius(), icon.getBigRadius(),
- icon.getSmallRadius(), icon.getSmallRadius(),
- icon.getSmallRadius(), icon.getSmallRadius()});
- drawCustomRoundedRect(canvas, bottomSide, new float[]{
- icon.getSmallRadius(), icon.getSmallRadius(),
- icon.getSmallRadius(), icon.getSmallRadius(),
- icon.getBigRadius(), icon.getBigRadius(),
- icon.getBigRadius(), icon.getBigRadius()});
- }
-
- /**
- * Draws a rectangle with custom rounded corners.
- * @param c The Canvas to draw on.
- * @param rect The bounds of the rectangle.
- * @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top
- * right y, bottom right x, and so on.
- */
- private void drawCustomRoundedRect(Canvas c, RectF rect, float[] radii) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- // Canvas.drawDoubleRoundRect is supported from Q onward
- c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, mBackgroundPaint);
- } else {
- // Fallback rectangle with uniform rounded corners
- c.drawRoundRect(rect, icon.getBigRadius(), icon.getBigRadius(), mBackgroundPaint);
- }
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.OPAQUE;
- }
-
- @Override
- public void setAlpha(int i) {
- mBackgroundPaint.setAlpha(i);
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- // Required by Drawable but not used.
- }
-}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconDrawable.java b/src/com/android/launcher3/apppairs/AppPairIconDrawable.java
new file mode 100644
index 0000000..db83d91
--- /dev/null
+++ b/src/com/android/launcher3/apppairs/AppPairIconDrawable.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.apppairs;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.icons.FastBitmapDrawable;
+
+/**
+ * A composed Drawable consisting of the two app pair icons and the background behind them (looks
+ * like two rectangles).
+ */
+public class AppPairIconDrawable extends Drawable {
+ private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final AppPairIconDrawingParams mP;
+ private final FastBitmapDrawable mIcon1;
+ private final FastBitmapDrawable mIcon2;
+
+ /**
+ * Null values to use with
+ * {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)}, since there
+ * doesn't seem to be any other API for drawing rectangles with 4 different corner radii.
+ */
+ private static final RectF EMPTY_RECT = new RectF();
+ private static final float[] ARRAY_OF_ZEROES = new float[8];
+
+ AppPairIconDrawable(
+ AppPairIconDrawingParams p, FastBitmapDrawable icon1, FastBitmapDrawable icon2) {
+ mP = p;
+ mBackgroundPaint.setStyle(Paint.Style.FILL);
+ mBackgroundPaint.setColor(p.getBgColor());
+ mIcon1 = icon1;
+ mIcon2 = icon2;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ if (mP.isLeftRightSplit()) {
+ drawLeftRightSplit(canvas);
+ } else {
+ drawTopBottomSplit(canvas);
+ }
+
+ canvas.translate(
+ mP.getStandardIconPadding() + mP.getOuterPadding(),
+ mP.getStandardIconPadding() + mP.getOuterPadding()
+ );
+
+ // Draw first icon.
+ canvas.save();
+ // The app icons are placed differently depending on device orientation.
+ if (mP.isLeftRightSplit()) {
+ canvas.translate(
+ mP.getInnerPadding(),
+ mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f
+ );
+ } else {
+ canvas.translate(
+ mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f,
+ mP.getInnerPadding()
+ );
+ }
+
+ mIcon1.draw(canvas);
+ canvas.restore();
+
+ // Draw second icon.
+ canvas.save();
+ // The app icons are placed differently depending on device orientation.
+ if (mP.isLeftRightSplit()) {
+ canvas.translate(
+ mP.getBackgroundSize() - (mP.getInnerPadding() + mP.getMemberIconSize()),
+ mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f
+ );
+ } else {
+ canvas.translate(
+ mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f,
+ mP.getBackgroundSize() - (mP.getInnerPadding() + mP.getMemberIconSize())
+ );
+ }
+
+ mIcon2.draw(canvas);
+ canvas.restore();
+ }
+
+ /**
+ * When device is in landscape, we draw the rectangles with a left-right split.
+ */
+ private void drawLeftRightSplit(Canvas canvas) {
+ // Get the bounds where we will draw the background image
+ int width = mP.getIconSize();
+ int height = mP.getIconSize();
+
+ // The left half of the background image, excluding center channel
+ RectF leftSide = new RectF(
+ mP.getStandardIconPadding() + mP.getOuterPadding(),
+ mP.getStandardIconPadding() + mP.getOuterPadding(),
+ (width / 2f) - (mP.getCenterChannelSize() / 2f),
+ height - (mP.getStandardIconPadding() + mP.getOuterPadding())
+ );
+ // The right half of the background image, excluding center channel
+ RectF rightSide = new RectF(
+ (width / 2f) + (mP.getCenterChannelSize() / 2f),
+ (mP.getStandardIconPadding() + mP.getOuterPadding()),
+ width - (mP.getStandardIconPadding() + mP.getOuterPadding()),
+ height - (mP.getStandardIconPadding() + mP.getOuterPadding())
+ );
+
+ drawCustomRoundedRect(canvas, leftSide, new float[]{
+ mP.getBigRadius(), mP.getBigRadius(),
+ mP.getSmallRadius(), mP.getSmallRadius(),
+ mP.getSmallRadius(), mP.getSmallRadius(),
+ mP.getBigRadius(), mP.getBigRadius()});
+ drawCustomRoundedRect(canvas, rightSide, new float[]{
+ mP.getSmallRadius(), mP.getSmallRadius(),
+ mP.getBigRadius(), mP.getBigRadius(),
+ mP.getBigRadius(), mP.getBigRadius(),
+ mP.getSmallRadius(), mP.getSmallRadius()});
+ }
+
+ /**
+ * When device is in portrait, we draw the rectangles with a top-bottom split.
+ */
+ private void drawTopBottomSplit(Canvas canvas) {
+ // Get the bounds where we will draw the background image
+ int width = mP.getIconSize();
+ int height = mP.getIconSize();
+
+ // The top half of the background image, excluding center channel
+ RectF topSide = new RectF(
+ (mP.getStandardIconPadding() + mP.getOuterPadding()),
+ (mP.getStandardIconPadding() + mP.getOuterPadding()),
+ width - (mP.getStandardIconPadding() + mP.getOuterPadding()),
+ (height / 2f) - (mP.getCenterChannelSize() / 2f)
+ );
+ // The bottom half of the background image, excluding center channel
+ RectF bottomSide = new RectF(
+ (mP.getStandardIconPadding() + mP.getOuterPadding()),
+ (height / 2f) + (mP.getCenterChannelSize() / 2f),
+ width - (mP.getStandardIconPadding() + mP.getOuterPadding()),
+ height - (mP.getStandardIconPadding() + mP.getOuterPadding())
+ );
+
+ drawCustomRoundedRect(canvas, topSide, new float[]{
+ mP.getBigRadius(), mP.getBigRadius(),
+ mP.getBigRadius(), mP.getBigRadius(),
+ mP.getSmallRadius(), mP.getSmallRadius(),
+ mP.getSmallRadius(), mP.getSmallRadius()});
+ drawCustomRoundedRect(canvas, bottomSide, new float[]{
+ mP.getSmallRadius(), mP.getSmallRadius(),
+ mP.getSmallRadius(), mP.getSmallRadius(),
+ mP.getBigRadius(), mP.getBigRadius(),
+ mP.getBigRadius(), mP.getBigRadius()});
+ }
+
+ /**
+ * Draws a rectangle with custom rounded corners.
+ * @param c The Canvas to draw on.
+ * @param rect The bounds of the rectangle.
+ * @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top
+ * right y, bottom right x, and so on.
+ */
+ private void drawCustomRoundedRect(Canvas c, RectF rect, float[] radii) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ // Canvas.drawDoubleRoundRect is supported from Q onward
+ c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, mBackgroundPaint);
+ } else {
+ // Fallback rectangle with uniform rounded corners
+ c.drawRoundRect(rect, mP.getBigRadius(), mP.getBigRadius(), mBackgroundPaint);
+ }
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.OPAQUE;
+ }
+
+ @Override
+ public void setAlpha(int i) {
+ mBackgroundPaint.setAlpha(i);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mBackgroundPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mP.getIconSize();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mP.getIconSize();
+ }
+}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt b/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt
new file mode 100644
index 0000000..45dc013
--- /dev/null
+++ b/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.apppairs
+
+import android.content.Context
+import com.android.launcher3.BubbleTextView.DISPLAY_FOLDER
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.R
+import com.android.launcher3.util.Themes
+import com.android.launcher3.views.ActivityContext
+
+class AppPairIconDrawingParams(val context: Context, container: Int) {
+ companion object {
+ // Design specs -- the below ratios are in relation to the size of a standard app icon.
+ // Note: The standard app icon has two sizes. One is the full size of the drawable (returned
+ // by dp.iconSizePx), and one is the visual size of the icon on-screen (11/12 of that).
+ // Hence the calculations below.
+ const val STANDARD_ICON_PADDING = 1 / 24f
+ const val STANDARD_ICON_SHRINK = 1 - STANDARD_ICON_PADDING * 2
+ // App pairs are slightly smaller than the *visual* size of a standard icon, so all ratios
+ // are calculated with that in mind.
+ const val OUTER_PADDING_SCALE = 1 / 30f * STANDARD_ICON_SHRINK
+ const val INNER_PADDING_SCALE = 1 / 24f * STANDARD_ICON_SHRINK
+ const val CENTER_CHANNEL_SCALE = 1 / 30f * STANDARD_ICON_SHRINK
+ const val BIG_RADIUS_SCALE = 1 / 5f * STANDARD_ICON_SHRINK
+ const val SMALL_RADIUS_SCALE = 1 / 15f * STANDARD_ICON_SHRINK
+ const val MEMBER_ICON_SCALE = 11 / 30f * STANDARD_ICON_SHRINK
+ }
+
+ // The size at which this graphic will be drawn.
+ val iconSize: Int
+ // Standard app icons are padded by this amount on each side.
+ val standardIconPadding: Float
+ // App pair icons are slightly smaller than regular icons, so we pad the icon by this much on
+ // each side.
+ val outerPadding: Float
+ // The colored background (two rectangles in a square area) is this big.
+ val backgroundSize: Float
+ // The size of the channel between the two halves of the app pair icon.
+ val centerChannelSize: Float
+ // The corner radius of the outside corners.
+ val bigRadius: Float
+ // The corner radius of the inside corners, touching the center channel.
+ val smallRadius: Float
+ // Inside of the icon, the two member apps are padded by this much.
+ val innerPadding: Float
+ // The two member apps have icons that are this big (in diameter).
+ val memberIconSize: Float
+ // The app pair icon appears differently in portrait and landscape.
+ var isLeftRightSplit: Boolean = true
+ // The background paint color (based on container).
+ var bgColor: Int = 0
+
+ init {
+ val activity: ActivityContext = ActivityContext.lookupContext(context)
+ val dp = activity.deviceProfile
+ iconSize = if (container == DISPLAY_FOLDER) dp.folderChildIconSizePx else dp.iconSizePx
+ standardIconPadding = iconSize * STANDARD_ICON_PADDING
+ outerPadding = iconSize * OUTER_PADDING_SCALE
+ backgroundSize = iconSize * STANDARD_ICON_SHRINK - (outerPadding * 2)
+ centerChannelSize = iconSize * CENTER_CHANNEL_SCALE
+ bigRadius = iconSize * BIG_RADIUS_SCALE
+ smallRadius = iconSize * SMALL_RADIUS_SCALE
+ innerPadding = iconSize * INNER_PADDING_SCALE
+ memberIconSize = iconSize * MEMBER_ICON_SCALE
+ updateOrientation(dp)
+ updateBgColor(container)
+ }
+
+ /** Checks the device orientation and updates isLeftRightSplit accordingly. */
+ fun updateOrientation(dp: DeviceProfile) {
+ isLeftRightSplit = dp.isLeftRightSplit
+ }
+
+ fun updateBgColor(container: Int) {
+ if (container == DISPLAY_FOLDER) {
+ bgColor = Themes.getAttrColor(context, R.attr.appPairSurfaceInFolder)
+ } else {
+ val ta = context.theme.obtainStyledAttributes(R.styleable.FolderIconPreview)
+ bgColor = ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0)
+ ta.recycle()
+ }
+ }
+}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index 365edf8..dce97eb 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -19,163 +19,124 @@
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
-import android.graphics.drawable.Drawable
import android.util.AttributeSet
-import android.util.Log
import android.view.Gravity
import android.widget.FrameLayout
+import androidx.annotation.OpenForTesting
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
import com.android.launcher3.icons.BitmapInfo
-import com.android.launcher3.icons.PlaceHolderIconDrawable
-import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.model.data.AppPairInfo
import com.android.launcher3.util.Themes
+import com.android.launcher3.views.ActivityContext
/**
* A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of
* two child UI elements on an [AppPairIcon], along with a BubbleTextView holding the text title.
*/
-class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
- FrameLayout(context, attrs) {
+@OpenForTesting
+open class AppPairIconGraphic
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) :
+ FrameLayout(context, attrs), OnDeviceProfileChangeListener {
private val TAG = "AppPairIconGraphic"
companion object {
- // Design specs -- the below ratios are in relation to the size of a standard app icon.
- private const val OUTER_PADDING_SCALE = 1 / 30f
- private const val INNER_PADDING_SCALE = 1 / 24f
- private const val MEMBER_ICON_SCALE = 11 / 30f
- private const val CENTER_CHANNEL_SCALE = 1 / 30f
- private const val BIG_RADIUS_SCALE = 1 / 5f
- private const val SMALL_RADIUS_SCALE = 1 / 15f
- // Disabled alpha is 38%, or 97/255
- private const val DISABLED_ALPHA = 97
- private const val ENABLED_ALPHA = 255
+ /** Composes a drawable for this icon, consisting of a background and 2 app icons. */
+ @JvmStatic
+ fun composeDrawable(
+ appPairInfo: AppPairInfo,
+ p: AppPairIconDrawingParams
+ ): AppPairIconDrawable {
+ // Generate new icons, using themed flag if needed.
+ val flags = if (Themes.isThemedIconEnabled(p.context)) BitmapInfo.FLAG_THEMED else 0
+ val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, flags)
+ val appIcon2 = appPairInfo.getSecondApp().newIcon(p.context, flags)
+ appIcon1.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
+ appIcon2.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
+
+ // If icons are unlaunchable due to screen size, manually override disabled appearance.
+ // (otherwise, leave disabled state alone; icons will naturally inherit the app's state)
+ val (isApp1Launchable, isApp2Launchable) = appPairInfo.isLaunchable(p.context)
+ if (!isApp1Launchable) appIcon1.setIsDisabled(true)
+ if (!isApp2Launchable) appIcon2.setIsDisabled(true)
+
+ // Create icon drawable.
+ val fullIconDrawable = AppPairIconDrawable(p, appIcon1, appIcon2)
+ fullIconDrawable.setBounds(0, 0, p.iconSize, p.iconSize)
+
+ return fullIconDrawable
+ }
}
- // App pair icons are slightly smaller than regular icons, so we pad the icon by this much on
- // each side.
- private var outerPadding = 0f
- // Inside of the icon, the two member apps are padded by this much.
- private var innerPadding = 0f
- // The colored background (two rectangles in a square area) is this big.
- private var backgroundSize = 0f
- // The two member apps have icons that are this big (in diameter).
- private var memberIconSize = 0f
- // The size of the center channel.
- var centerChannelSize = 0f
- // The large outer radius of the background rectangles.
- var bigRadius = 0f
- // The small inner radius of the background rectangles.
- var smallRadius = 0f
- // The app pairs icon appears differently in portrait and landscape.
- var isLeftRightSplit = false
-
private lateinit var parentIcon: AppPairIcon
- private lateinit var appPairBackground: Drawable
- private var appIcon1: Drawable? = null
- private var appIcon2: Drawable? = null
+ private lateinit var drawParams: AppPairIconDrawingParams
+ lateinit var drawable: AppPairIconDrawable
- fun init(grid: DeviceProfile, icon: AppPairIcon) {
- // Calculate device-specific measurements
- val defaultIconSize = grid.iconSizePx
- outerPadding = OUTER_PADDING_SCALE * defaultIconSize
- innerPadding = INNER_PADDING_SCALE * defaultIconSize
- backgroundSize = defaultIconSize - outerPadding * 2
- memberIconSize = MEMBER_ICON_SCALE * defaultIconSize
- centerChannelSize = CENTER_CHANNEL_SCALE * defaultIconSize
- bigRadius = BIG_RADIUS_SCALE * defaultIconSize
- smallRadius = SMALL_RADIUS_SCALE * defaultIconSize
- isLeftRightSplit = grid.isLeftRightSplit
+ fun init(icon: AppPairIcon, container: Int) {
parentIcon = icon
-
- appPairBackground = AppPairIconBackground(context, this)
- appPairBackground.setBounds(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
- applyIcons(parentIcon.info.contents)
+ drawParams = AppPairIconDrawingParams(context, container)
+ drawable = composeDrawable(icon.info, drawParams)
// Center the drawable area in the larger icon canvas
val lp: LayoutParams = layoutParams as LayoutParams
lp.gravity = Gravity.CENTER_HORIZONTAL
- lp.topMargin = outerPadding.toInt()
- lp.height = backgroundSize.toInt()
- lp.width = backgroundSize.toInt()
+ lp.height = drawParams.iconSize
+ lp.width = drawParams.iconSize
layoutParams = lp
}
- /** Sets up app pair member icons for drawing. */
- private fun applyIcons(contents: ArrayList<WorkspaceItemInfo>) {
- // App pair should always contain 2 members; if not 2, return to avoid a crash loop
- if (contents.size != 2) {
- Log.wtf(TAG, "AppPair contents not 2, size: " + contents.size, Throwable())
- return
- }
-
- // Generate new icons, using themed flag if needed
- val flags = if (Themes.isThemedIconEnabled(context)) BitmapInfo.FLAG_THEMED else 0
- val newIcon1 = parentIcon.info.contents[0].newIcon(context, flags)
- val newIcon2 = parentIcon.info.contents[1].newIcon(context, flags)
-
- // If app icons did not draw fully last time, animate to full icon
- (appIcon1 as? PlaceHolderIconDrawable)?.animateIconUpdate(newIcon1)
- (appIcon2 as? PlaceHolderIconDrawable)?.animateIconUpdate(newIcon2)
-
- appIcon1 = newIcon1
- appIcon2 = newIcon2
- appIcon1?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
- appIcon2?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ getActivityContext().addOnDeviceProfileChangeListener(this)
}
- /** Gets this icon graphic's bounds, with respect to the parent icon's coordinate system. */
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ getActivityContext().removeOnDeviceProfileChangeListener(this)
+ }
+
+ private fun getActivityContext(): ActivityContext {
+ return ActivityContext.lookupContext(context)
+ }
+
+ /** When device profile changes, update orientation */
+ override fun onDeviceProfileChanged(dp: DeviceProfile) {
+ drawParams.updateOrientation(dp)
+ redraw()
+ }
+
+ /**
+ * When the icon is temporary moved to a different colored surface, update the background color.
+ * Calling this method with [null] reverts the icon back to its default color.
+ */
+ fun onTemporaryContainerChange(newContainer: Int?) {
+ drawParams.updateBgColor(newContainer ?: parentIcon.container)
+ redraw()
+ }
+
+ /**
+ * Gets this icon graphic's visual bounds, with respect to the parent icon's coordinate system.
+ */
fun getIconBounds(outBounds: Rect) {
- outBounds.set(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
+ outBounds.set(0, 0, drawParams.backgroundSize.toInt(), drawParams.backgroundSize.toInt())
outBounds.offset(
// x-coordinate in parent's coordinate system
- ((parentIcon.width - backgroundSize) / 2).toInt(),
+ ((parentIcon.width - drawParams.backgroundSize) / 2).toInt(),
// y-coordinate in parent's coordinate system
- parentIcon.paddingTop + outerPadding.toInt()
+ (parentIcon.paddingTop + drawParams.standardIconPadding + drawParams.outerPadding)
+ .toInt()
)
}
+ /** Updates the icon drawable and redraws it */
+ fun redraw() {
+ drawable = composeDrawable(parentIcon.info, drawParams)
+ invalidate()
+ }
+
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
-
- val drawAlpha =
- if (!parentIcon.isLaunchableAtScreenSize || parentIcon.info.isDisabled) DISABLED_ALPHA
- else ENABLED_ALPHA
-
- // Draw background
- appPairBackground.alpha = drawAlpha
- appPairBackground.draw(canvas)
-
- // Make sure icons are loaded and fresh
- applyIcons(parentIcon.info.contents)
-
- // Draw first icon
- canvas.save()
- // The app icons are placed differently depending on device orientation.
- if (isLeftRightSplit) {
- canvas.translate(innerPadding, height / 2f - memberIconSize / 2f)
- } else {
- canvas.translate(width / 2f - memberIconSize / 2f, innerPadding)
- }
- appIcon1?.alpha = drawAlpha
- appIcon1?.draw(canvas)
- canvas.restore()
-
- // Draw second icon
- canvas.save()
- // The app icons are placed differently depending on device orientation.
- if (isLeftRightSplit) {
- canvas.translate(
- width - (innerPadding + memberIconSize),
- height / 2f - memberIconSize / 2f
- )
- } else {
- canvas.translate(
- width / 2f - memberIconSize / 2f,
- height - (innerPadding + memberIconSize)
- )
- }
- appIcon2?.alpha = drawAlpha
- appIcon2?.draw(canvas)
- canvas.restore()
+ drawable.draw(canvas)
}
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 262b2a1..33e6f91 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -16,32 +16,16 @@
package com.android.launcher3.config;
-import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
-import static com.android.launcher3.config.FeatureFlags.FlagState.DISABLED;
-import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
-import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
-import static com.android.launcher3.uioverrides.flags.FlagsFactory.getDebugFlag;
-import static com.android.launcher3.uioverrides.flags.FlagsFactory.getReleaseFlag;
+import static com.android.launcher3.config.FeatureFlags.BooleanFlag.DISABLED;
+import static com.android.launcher3.config.FeatureFlags.BooleanFlag.ENABLED;
import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
import android.content.res.Resources;
-import android.view.ViewConfiguration;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.BuildConfig;
import com.android.launcher3.Flags;
-import com.android.launcher3.uioverrides.flags.FlagsFactory;
-
-import java.util.function.Predicate;
-import java.util.function.ToIntFunction;
/**
* Defines a set of flags used to control various launcher behaviors.
@@ -50,11 +34,6 @@
*/
public final class FeatureFlags {
- @VisibleForTesting
- public static Predicate<BooleanFlag> sBooleanReader = f -> f.mCurrentValue;
- @VisibleForTesting
- public static ToIntFunction<IntFlag> sIntReader = f -> f.mCurrentValue;
-
private FeatureFlags() { }
/**
@@ -82,15 +61,6 @@
* and set a default value for the flag. This will be the default value on Debug builds.
* <p>
*/
- // TODO(Block 1): Clean up flags
- public static final BooleanFlag ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES = getReleaseFlag(
- 270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", ENABLED,
- "Enable option to replace decorator-based search result backgrounds with drawables");
-
- public static final BooleanFlag ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION = getReleaseFlag(
- 270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", ENABLED,
- "Enable option to launch search results using the new view container transitions");
-
// TODO(Block 2): Clean up flags
public static final BooleanFlag ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH = getDebugFlag(270395073,
"ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH", DISABLED,
@@ -100,13 +70,6 @@
public static final BooleanFlag ENABLE_DISMISS_PREDICTION_UNDO = getDebugFlag(270394476,
"ENABLE_DISMISS_PREDICTION_UNDO", DISABLED,
"Show an 'Undo' snackbar when users dismiss a predicted hotseat item");
-
- public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag(
- 251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED,
- "Marks LauncherPref data as (and allows it to) available while the device is"
- + " locked. Enabling this causes a 1-time movement of certain SharedPreferences"
- + " data. Improves startup latency.");
-
public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(270395171,
"CONTINUOUS_VIEW_TREE_CAPTURE", ENABLED, "Capture View tree every frame");
@@ -121,37 +84,10 @@
+ "data preparation for loading the home screen");
// TODO(Block 4): Cleanup flags
- public static final BooleanFlag ENABLE_FLOATING_SEARCH_BAR =
- getReleaseFlag(268388460, "ENABLE_FLOATING_SEARCH_BAR", DISABLED,
- "Allow search bar to persist and animate across states, and attach to"
- + " the keyboard from the bottom of the screen");
-
public static final BooleanFlag ENABLE_ALL_APPS_FROM_OVERVIEW =
getDebugFlag(275132633, "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED,
"Allow entering All Apps from Overview (e.g. long swipe up from app)");
- public static final BooleanFlag CUSTOM_LPNH_THRESHOLDS =
- getReleaseFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", DISABLED,
- "Add dev options to customize the LPNH trigger slop and milliseconds");
-
- public static final BooleanFlag ANIMATE_LPNH =
- getReleaseFlag(308693847, "ANIMATE_LPNH", TEAMFOOD,
- "Animates navbar when long pressing");
-
- public static final BooleanFlag SHRINK_NAV_HANDLE_ON_PRESS =
- getReleaseFlag(314158312, "SHRINK_NAV_HANDLE_ON_PRESS", DISABLED,
- "Shrinks navbar when long pressing if ANIMATE_LPNH is enabled");
-
- public static final IntFlag LPNH_SLOP_PERCENTAGE =
- FlagsFactory.getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
- "Controls touch slop percentage for lpnh",
- LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE);
-
- public static final IntFlag LPNH_TIMEOUT_MS =
- FlagsFactory.getIntFlag(301680992, "LPNH_TIMEOUT_MS",
- ViewConfiguration.getLongPressTimeout(),
- "Controls lpnh timeout in milliseconds", LONG_PRESS_NAV_HANDLE_TIMEOUT_MS);
-
public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", ENABLED,
"Enable option to show keyboard when going to all-apps");
@@ -177,11 +113,6 @@
"SECONDARY_DRAG_N_DROP_TO_PIN", DISABLED,
"Enable dragging and dropping to pin apps within secondary display");
- // TODO(Block 7): Clean up flags
- public static final BooleanFlag ENABLE_FORCED_MONO_ICON = getDebugFlag(270396209,
- "ENABLE_FORCED_MONO_ICON", DISABLED,
- "Enable the ability to generate monochromatic icons, if it is not provided by the app");
-
// TODO(Block 8): Clean up flags
// TODO(Block 9): Clean up flags
@@ -189,17 +120,6 @@
"MULTI_SELECT_EDIT_MODE", DISABLED, "Enable new multi-select edit mode "
+ "for home screen");
- public static final BooleanFlag SMARTSPACE_AS_A_WIDGET = getDebugFlag(299181941,
- "SMARTSPACE_AS_A_WIDGET", DISABLED, "Enable SmartSpace as a widget");
-
- public static boolean shouldShowFirstPageWidget() {
- return SMARTSPACE_AS_A_WIDGET.get() && WIDGET_ON_FIRST_SCREEN;
- }
-
- public static final BooleanFlag ENABLE_SMARTSPACE_REMOVAL = getDebugFlag(290799975,
- "ENABLE_SMARTSPACE_REMOVAL", DISABLED, "Enable SmartSpace removal for "
- + "home screen");
-
// TODO(Block 11): Clean up flags
public static final BooleanFlag FOLDABLE_SINGLE_PAGE = getDebugFlag(270395274,
"FOLDABLE_SINGLE_PAGE", DISABLED, "Use a single page for the workspace");
@@ -220,7 +140,7 @@
// TODO(Block 14): Cleanup flags
public static final BooleanFlag NOTIFY_CRASHES = getDebugFlag(270393108, "NOTIFY_CRASHES",
- TEAMFOOD, "Sends a notification whenever launcher encounters an uncaught exception.");
+ DISABLED, "Sends a notification whenever launcher encounters an uncaught exception.");
public static final boolean ENABLE_TASKBAR_NAVBAR_UNIFICATION =
enableTaskbarNavbarUnification() && !isPhone();
@@ -269,9 +189,6 @@
"ENABLE_EXPANDING_PAUSE_WORK_BUTTON", DISABLED,
"Expand and collapse pause work button while scrolling");
- public static final BooleanFlag COLLECT_SEARCH_HISTORY = getReleaseFlag(270391455,
- "COLLECT_SEARCH_HISTORY", DISABLED, "Allow launcher to collect search history for log");
-
// Aconfig migration complete for ENABLE_TWOLINE_ALLAPPS.
public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(270390937,
"ENABLE_TWOLINE_ALLAPPS", DISABLED, "Enables two line label inside all apps.");
@@ -287,53 +204,10 @@
"INJECT_FALLBACK_APP_CORPUS_RESULTS", DISABLED,
"Inject fallback app corpus result when AiAi fails to return it.");
- public static final BooleanFlag ENABLE_LONG_PRESS_NAV_HANDLE =
- getReleaseFlag(299682306, "ENABLE_LONG_PRESS_NAV_HANDLE", ENABLED,
- "Enables long pressing on the bottom bar nav handle to trigger events.");
-
- public static final BooleanFlag ENABLE_SEARCH_HAPTIC_HINT =
- getReleaseFlag(314005131, "ENABLE_SEARCH_HAPTIC_HINT", ENABLED,
- "Enables haptic hint while long pressing on the bottom bar nav handle.");
-
- public static final BooleanFlag ENABLE_SEARCH_HAPTIC_COMMIT =
- getReleaseFlag(314005577, "ENABLE_SEARCH_HAPTIC_COMMIT", ENABLED,
- "Enables haptic hint at end of long pressing on the bottom bar nav handle.");
-
- public static final IntFlag LPNH_HAPTIC_HINT_START_SCALE_PERCENT =
- FlagsFactory.getIntFlag(309972570,
- "LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0,
- "Haptic hint start scale.",
- LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT);
-
- public static final IntFlag LPNH_HAPTIC_HINT_END_SCALE_PERCENT =
- FlagsFactory.getIntFlag(309972570,
- "LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100,
- "Haptic hint end scale.", LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT);
-
- public static final IntFlag LPNH_HAPTIC_HINT_SCALE_EXPONENT =
- FlagsFactory.getIntFlag(309972570,
- "LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1,
- "Haptic hint scale exponent.",
- LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT);
-
- public static final IntFlag LPNH_HAPTIC_HINT_ITERATIONS =
- FlagsFactory.getIntFlag(309972570, "LPNH_HAPTIC_HINT_ITERATIONS",
- 50,
- "Haptic hint number of iterations.",
- LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS);
-
- public static final BooleanFlag ENABLE_LPNH_DEEP_PRESS =
- getReleaseFlag(310952290, "ENABLE_LPNH_DEEP_PRESS", ENABLED,
- "Long press of nav handle is instantly triggered if deep press is detected.");
-
- public static final IntFlag LPNH_HAPTIC_HINT_DELAY =
- FlagsFactory.getIntFlag(309972570, "LPNH_HAPTIC_HINT_DELAY", 0,
- "Delay before haptic hint starts.", LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY);
-
// TODO(Block 17): Clean up flags
// Aconfig migration complete for ENABLE_TASKBAR_PINNING.
private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(296231746,
- "ENABLE_TASKBAR_PINNING", TEAMFOOD,
+ "ENABLE_TASKBAR_PINNING", DISABLED,
"Enables taskbar pinning to allow user to switch between transient and persistent "
+ "taskbar flavors");
@@ -373,7 +247,7 @@
// Aconfig migration complete for ENABLE_HOME_TRANSITION_LISTENER.
public static final BooleanFlag ENABLE_HOME_TRANSITION_LISTENER = getDebugFlag(306053414,
- "ENABLE_HOME_TRANSITION_LISTENER", TEAMFOOD,
+ "ENABLE_HOME_TRANSITION_LISTENER", DISABLED,
"Enables launcher to listen to all transitions that include home activity.");
public static boolean enableHomeTransitionListener() {
@@ -395,11 +269,6 @@
+ "waiting for SystemUI and then merging the SystemUI progress whenever we "
+ "start receiving the events");
- // TODO(Block 24): Clean up flags
- public static final BooleanFlag ENABLE_NEW_MIGRATION_LOGIC = getDebugFlag(270393455,
- "ENABLE_NEW_MIGRATION_LOGIC", ENABLED,
- "Enable the new grid migration logic, keeping pages when src < dest");
-
// TODO(Block 25): Clean up flags
public static final BooleanFlag ENABLE_NEW_GESTURE_NAV_TUTORIAL = getDebugFlag(270396257,
"ENABLE_NEW_GESTURE_NAV_TUTORIAL", ENABLED,
@@ -473,7 +342,7 @@
// Aconfig migration complete for ENABLE_RESPONSIVE_WORKSPACE.
@VisibleForTesting
public static final BooleanFlag ENABLE_RESPONSIVE_WORKSPACE = getDebugFlag(241386436,
- "ENABLE_RESPONSIVE_WORKSPACE", TEAMFOOD,
+ "ENABLE_RESPONSIVE_WORKSPACE", DISABLED,
"Enables new workspace grid calculations method.");
public static boolean enableResponsiveWorkspace() {
return ENABLE_RESPONSIVE_WORKSPACE.get() || Flags.enableResponsiveWorkspace();
@@ -487,51 +356,31 @@
"ALL_APPS_GONE_VISIBILITY", ENABLED,
"Set all apps container view's hidden visibility to GONE instead of INVISIBLE.");
- // TODO(Block 34): Empty block
- // Please only add flags to your assigned block. If you do not have a block:
- // 1. Assign yourself this block
- // 2. Add your flag to this block
- // 3. Add a new empty block below this one
- // 4. Move this comment to that new empty block
- // This is all to prevent merge conflicts in the future and help keep track of who owns which
- // flags.
- // List of assigned blocks can be found: http://go/gnl-flags-block-directory
-
- public static class BooleanFlag {
-
- private final boolean mCurrentValue;
-
- public BooleanFlag(boolean currentValue) {
- mCurrentValue = currentValue;
- }
-
- public boolean get() {
- return sBooleanReader.test(this);
- }
+ public static BooleanFlag getDebugFlag(
+ int bugId, String key, BooleanFlag flagState, String description) {
+ return flagState;
}
- /**
- * Class representing an integer flag
- */
- public static class IntFlag {
-
- private final int mCurrentValue;
-
- public IntFlag(int currentValue) {
- mCurrentValue = currentValue;
- }
-
- public int get() {
- return sIntReader.applyAsInt(this);
- }
+ public static BooleanFlag getReleaseFlag(
+ int bugId, String key, BooleanFlag flagState, String description) {
+ return flagState;
}
/**
* Enabled state for a flag
*/
- public enum FlagState {
- ENABLED,
- DISABLED,
- TEAMFOOD // Enabled in team food
+ public enum BooleanFlag {
+ ENABLED(true),
+ DISABLED(false);
+
+ private final boolean mValue;
+
+ BooleanFlag(boolean value) {
+ mValue = value;
+ }
+
+ public boolean get() {
+ return mValue;
+ }
}
}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 213c458..85eb39b 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -67,7 +67,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.pm.PinRequestHelper;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.views.AbstractSlideInView;
@@ -91,7 +91,8 @@
*/
@TargetApi(Build.VERSION_CODES.O)
public class AddItemActivity extends BaseActivity
- implements OnLongClickListener, OnTouchListener, AbstractSlideInView.OnCloseListener {
+ implements OnLongClickListener, OnTouchListener, AbstractSlideInView.OnCloseListener,
+ WidgetCell.PreviewReadyListener {
private static final int SHADOW_SIZE = 10;
@@ -142,6 +143,7 @@
mDragLayer = findViewById(R.id.add_item_drag_layer);
mDragLayer.recreateControllers();
mWidgetCell = findViewById(R.id.widget_cell);
+ mWidgetCell.addPreviewReadyListener(this);
mAccessibilityManager =
getApplicationContext().getSystemService(AccessibilityManager.class);
@@ -162,7 +164,7 @@
finish();
return;
}
- ApplicationInfo info = new PackageManagerHelper(this)
+ ApplicationInfo info = PackageManagerHelper.INSTANCE.get(this)
.getApplicationInfo(targetApp.packageName, targetApp.user, 0);
if (info == null) {
finish();
@@ -258,8 +260,9 @@
.addCategory(Intent.CATEGORY_HOME)
.setPackage(getPackageName())
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Launcher.ACTIVITY_TRACKER.registerCallback(listener);
- startActivity(homeIntent, ApiWrapper.createFadeOutAnimOptions(this).toBundle());
+ Launcher.ACTIVITY_TRACKER.registerCallback(listener, "AddItemActivity.onLongClick");
+ startActivity(homeIntent,
+ ApiWrapper.INSTANCE.get(this).createFadeOutAnimOptions().toBundle());
logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED);
mFinishOnPause = true;
return false;
@@ -453,4 +456,11 @@
.withItemInfo((ItemInfo) mWidgetCell.getWidgetView().getTag())
.log(command);
}
+
+ @Override
+ public void onPreviewAvailable() {
+ // Set the preview height based on "the only" widget's preview.
+ mWidgetCell.setParentAlignedPreviewHeight(mWidgetCell.getPreviewContentHeight());
+ mWidgetCell.post(mWidgetCell::requestLayout);
+ }
}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index b6e5977..bc5a164 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -33,6 +33,7 @@
import com.android.launcher3.DropTarget;
import com.android.launcher3.Flags;
import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -289,7 +290,8 @@
// Cancel the current drag if we are removing an app that we are dragging
if (mDragObject != null) {
ItemInfo dragInfo = mDragObject.dragInfo;
- if (dragInfo instanceof WorkspaceItemInfo && matcher.test(dragInfo)) {
+ if ((dragInfo instanceof WorkspaceItemInfo && matcher.test(dragInfo))
+ || (dragInfo instanceof AppPairInfo api && api.anyMatch(matcher))) {
cancelDrag();
}
}
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index f3708a2..29fc613 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -28,6 +28,7 @@
import android.view.View;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DragSource;
@@ -36,6 +37,7 @@
import com.android.launcher3.R;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.widget.util.WidgetDragScaleUtils;
/**
* Drag controller for Launcher activity
@@ -43,7 +45,6 @@
public class LauncherDragController extends DragController<Launcher> {
private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
-
private final FlingToDeleteHelper mFlingToDeleteHelper;
public LauncherDragController(Launcher launcher) {
@@ -92,8 +93,13 @@
&& !mOptions.preDragCondition.shouldStartDrag(0);
final Resources res = mActivity.getResources();
- final float scaleDps = mIsInPreDrag
- ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
+
+ final float scalePx;
+ if (originalView.getViewType() == DraggableView.DRAGGABLE_WIDGET) {
+ scalePx = mIsInPreDrag ? 0f : getWidgetDragScalePx(drawable, view, dragInfo);
+ } else {
+ scalePx = mIsInPreDrag ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
+ }
final DragView dragView = mDragObject.dragView = drawable != null
? new LauncherDragView(
mActivity,
@@ -102,7 +108,7 @@
registrationY,
initialDragViewScale,
dragViewScaleOnDrop,
- scaleDps)
+ scalePx)
: new LauncherDragView(
mActivity,
view,
@@ -112,7 +118,7 @@
registrationY,
initialDragViewScale,
dragViewScaleOnDrop,
- scaleDps);
+ scalePx);
dragView.setItemInfo(dragInfo);
mDragObject.dragComplete = false;
@@ -157,6 +163,29 @@
return dragView;
}
+
+ /**
+ * Returns the scale in terms of pixels (to be applied on width) to scale the preview
+ * during drag and drop.
+ */
+ @VisibleForTesting
+ float getWidgetDragScalePx(@Nullable Drawable drawable, @Nullable View view,
+ ItemInfo dragInfo) {
+ float draggedViewWidthPx = 0;
+ float draggedViewHeightPx = 0;
+
+ if (view != null) {
+ draggedViewWidthPx = view.getMeasuredWidth();
+ draggedViewHeightPx = view.getMeasuredHeight();
+ } else if (drawable != null) {
+ draggedViewWidthPx = drawable.getIntrinsicWidth();
+ draggedViewHeightPx = drawable.getIntrinsicHeight();
+ }
+
+ return WidgetDragScaleUtils.getWidgetDragScalePx(mActivity, mActivity.getDeviceProfile(),
+ draggedViewWidthPx, draggedViewHeightPx, dragInfo);
+ }
+
@Override
protected void exitDrag() {
if (!mActivity.isInState(EDIT_MODE)) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index ecb5c8f..dcc55e6 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -19,6 +19,9 @@
import static android.text.TextUtils.isEmpty;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+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.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
@@ -66,14 +69,12 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Alarm;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
@@ -166,6 +167,22 @@
private static final Rect sTempRect = new Rect();
private static final int MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10;
+ /**
+ * Checks if {@code o} is an {@link ItemInfo} type that can be placed in folders.
+ */
+ public static boolean willAccept(Object o) {
+ return o instanceof ItemInfo info && willAcceptItemType(info.itemType);
+ }
+
+ /**
+ * Checks if {@code itemType} is a type that can be placed in folders.
+ */
+ public static boolean willAcceptItemType(int itemType) {
+ return itemType == ITEM_TYPE_APPLICATION
+ || itemType == ITEM_TYPE_DEEP_SHORTCUT
+ || itemType == ITEM_TYPE_APP_PAIR;
+ }
+
private final Alarm mReorderAlarm = new Alarm(Looper.getMainLooper());
private final Alarm mOnExitAlarm = new Alarm(Looper.getMainLooper());
private final Alarm mOnScrollHintAlarm = new Alarm(Looper.getMainLooper());
@@ -284,6 +301,8 @@
mContent.setFolder(this);
mPageIndicator = findViewById(R.id.folder_page_indicator);
+ mFooter = findViewById(R.id.folder_footer);
+ mFooterHeight = dp.folderFooterHeightPx;
mFolderName = findViewById(R.id.folder_name);
mFolderName.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp.folderLabelTextSizePx);
mFolderName.setOnBackKeyListener(this);
@@ -294,9 +313,10 @@
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
| InputType.TYPE_TEXT_FLAG_CAP_WORDS);
mFolderName.forceDisableSuggestions(true);
-
- mFooter = findViewById(R.id.folder_footer);
- mFooterHeight = dp.folderFooterHeightPx;
+ mFolderName.setPadding(mFolderName.getPaddingLeft(),
+ (mFooterHeight - mFolderName.getLineHeight()) / 2,
+ mFolderName.getPaddingRight(),
+ (mFooterHeight - mFolderName.getLineHeight()) / 2);
mKeyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this);
setWindowInsetsAnimationCallback(mKeyboardInsetAnimationCallback);
@@ -310,9 +330,7 @@
public boolean startDrag(View v, DragOptions options) {
Object tag = v.getTag();
- if (tag instanceof WorkspaceItemInfo) {
- WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
-
+ if (tag instanceof ItemInfo item) {
mEmptyCellRank = item.rank;
mCurrentDragView = v;
@@ -343,14 +361,12 @@
}
mContent.removeItem(mCurrentDragView);
- if (dragObject.dragInfo instanceof WorkspaceItemInfo) {
- mItemsInvalidated = true;
+ mItemsInvalidated = true;
- // We do not want to get events for the item being removed, as they will get handled
- // when the drop completes
- try (SuppressInfoChanges s = new SuppressInfoChanges()) {
- mInfo.remove((WorkspaceItemInfo) dragObject.dragInfo, true);
- }
+ // We do not want to get events for the item being removed, as they will get handled
+ // when the drop completes
+ try (SuppressInfoChanges s = new SuppressInfoChanges()) {
+ mInfo.remove(dragObject.dragInfo, true);
}
mDragInProgress = true;
mItemAddedBackToSelfViaIcon = false;
@@ -490,7 +506,7 @@
mInfo = info;
mFromTitle = info.title;
mFromLabelState = info.getFromLabelState();
- ArrayList<WorkspaceItemInfo> children = info.contents;
+ ArrayList<ItemInfo> children = info.getContents();
Collections.sort(children, ITEM_POS_COMPARATOR);
updateItemLocationsInDatabaseBatch(true);
@@ -623,7 +639,7 @@
// onDropComplete. Perform cleanup once drag-n-drop ends.
mDragController.addDragListener(this);
- ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.contents);
+ ArrayList<ItemInfo> items = new ArrayList<>(mInfo.getContents());
mEmptyCellRank = items.size();
items.add(null); // Add an empty spot at the end
@@ -636,7 +652,7 @@
* is played.
*/
public void animateOpen() {
- animateOpen(mInfo.contents, 0);
+ animateOpen(mInfo.getContents(), 0);
}
/**
@@ -644,7 +660,7 @@
* is animated relative to the specified View. If the View is null, no animation
* is played.
*/
- private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
+ private void animateOpen(List<ItemInfo> items, int pageNo) {
if (items == null || items.size() <= 1) {
Log.d(TAG, "Couldn't animate folder open because items is: " + items);
return;
@@ -893,8 +909,7 @@
public boolean acceptDrop(DragObject d) {
final ItemInfo item = d.dragInfo;
final int itemType = item.itemType;
- return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
- itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT));
+ return Folder.willAcceptItemType(itemType);
}
public void onDragEnter(DragObject d) {
@@ -1047,7 +1062,7 @@
}
} else {
// The drag failed, we need to return the item to the folder
- WorkspaceItemInfo info = (WorkspaceItemInfo) d.dragInfo;
+ ItemInfo info = d.dragInfo;
View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
? mCurrentDragView : mContent.createNewView(info);
ArrayList<View> views = getIconsInReadingOrder();
@@ -1094,9 +1109,9 @@
mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
ArrayList<ItemInfo> items = new ArrayList<>();
- int total = mInfo.contents.size();
+ int total = mInfo.getContents().size();
for (int i = 0; i < total; i++) {
- WorkspaceItemInfo itemInfo = mInfo.contents.get(i);
+ ItemInfo itemInfo = mInfo.getContents().get(i);
if (verifier.updateRankAndPos(itemInfo, i)) {
items.add(itemInfo);
}
@@ -1109,8 +1124,7 @@
Executors.MODEL_EXECUTOR.post(() -> {
FolderNameInfos nameInfos = new FolderNameInfos();
FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
- fnp.getSuggestedFolderName(
- getContext(), mInfo.contents, nameInfos);
+ fnp.getSuggestedFolderName(getContext(), mInfo.getAppContents(), nameInfos);
mInfo.suggestedFolderNames = nameInfos;
});
}
@@ -1214,7 +1228,7 @@
}
public int getItemCount() {
- return mInfo.contents.size();
+ return mInfo.getContents().size();
}
void replaceFolderWithFinalItem() {
@@ -1295,15 +1309,15 @@
d.deferDragViewCleanupPostAnimation = false;
mRearrangeOnClose = true;
} else {
- final WorkspaceItemInfo si;
+ final ItemInfo si;
if (pasiSi != null) {
si = pasiSi;
} else if (d.dragInfo instanceof WorkspaceItemFactory) {
// Came from all apps -- make a copy.
si = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(launcher);
} else {
- // WorkspaceItemInfo
- si = (WorkspaceItemInfo) d.dragInfo;
+ // WorkspaceItemInfo or AppPairInfo
+ si = d.dragInfo;
}
View currentDragView;
@@ -1311,7 +1325,7 @@
currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank);
// Actually move the item in the database if it was an external drag. Call this
- // before creating the view, so that WorkspaceItemInfo is updated appropriately.
+ // before creating the view, so that the ItemInfo is updated appropriately.
mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(
si, mInfo.id, 0, si.cellX, si.cellY);
mIsExternalDrag = false;
@@ -1373,14 +1387,14 @@
// This is used so the item doesn't immediately appear in the folder when added. In one case
// we need to create the illusion that the item isn't added back to the folder yet, to
// to correspond to the animation of the icon back into the folder. This is
- public void hideItem(WorkspaceItemInfo info) {
+ public void hideItem(ItemInfo info) {
View v = getViewForInfo(info);
if (v != null) {
v.setVisibility(INVISIBLE);
}
}
- public void showItem(WorkspaceItemInfo info) {
+ public void showItem(ItemInfo info) {
View v = getViewForInfo(info);
if (v != null) {
v.setVisibility(VISIBLE);
@@ -1388,7 +1402,7 @@
}
@Override
- public void onAdd(WorkspaceItemInfo item, int rank) {
+ public void onAdd(ItemInfo item, int rank) {
FolderGridOrganizer verifier = new FolderGridOrganizer(
mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
verifier.updateRankAndPos(item, rank);
@@ -1403,7 +1417,7 @@
}
@Override
- public void onRemove(List<WorkspaceItemInfo> items) {
+ public void onRemove(List<ItemInfo> items) {
mItemsInvalidated = true;
items.stream().map(this::getViewForInfo).forEach(mContent::removeItem);
if (mState == STATE_ANIMATING) {
@@ -1420,7 +1434,7 @@
}
}
- private View getViewForInfo(final WorkspaceItemInfo item) {
+ private View getViewForInfo(final ItemInfo item) {
return mContent.iterateOverItems((info, view) -> info == item);
}
@@ -1429,6 +1443,11 @@
updateTextViewFocus();
}
+ @Override
+ public void onTitleChanged(CharSequence title) {
+ mFolderName.setText(title);
+ }
+
/**
* Utility methods to iterate over items of the view
*/
@@ -1448,7 +1467,7 @@
return mItemsInReadingOrder;
}
- public List<BubbleTextView> getItemsOnPage(int page) {
+ public List<View> getItemsOnPage(int page) {
ArrayList<View> allItems = getIconsInReadingOrder();
int lastPage = mContent.getPageCount() - 1;
int totalItemsInFolder = allItems.size();
@@ -1460,9 +1479,9 @@
int startIndex = page * itemsPerPage;
int endIndex = Math.min(startIndex + numItemsOnCurrentPage, allItems.size());
- List<BubbleTextView> itemsOnCurrentPage = new ArrayList<>(numItemsOnCurrentPage);
+ List<View> itemsOnCurrentPage = new ArrayList<>(numItemsOnCurrentPage);
for (int i = startIndex; i < endIndex; ++i) {
- itemsOnCurrentPage.add((BubbleTextView) allItems.get(i));
+ itemsOnCurrentPage.add(allItems.get(i));
}
return itemsOnCurrentPage;
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index a91373b..9824992 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-import static com.android.launcher3.graphics.IconShape.getShape;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -43,7 +42,10 @@
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyResetListener;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
+import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.graphics.IconShape.ShapeDelegate;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
@@ -127,7 +129,7 @@
(BaseDragLayer.LayoutParams) mFolder.getLayoutParams();
mFolderIcon.getPreviewItemManager().recomputePreviewDrawingParams();
ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
- final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0);
+ final List<View> itemsInPreview = getPreviewIconsOnPage(0);
// Match position of the FolderIcon
final Rect folderIconPos = new Rect();
@@ -139,8 +141,8 @@
// Match size/scale of icons in the preview
float previewScale = rule.scaleForItem(itemsInPreview.size());
float previewSize = rule.getIconSize() * previewScale;
- float initialScale = previewSize / itemsInPreview.get(0).getIconSize()
- * scaleRelativeToDragLayer;
+ float baseIconSize = getBubbleTextView(itemsInPreview.get(0)).getIconSize();
+ float initialScale = previewSize / baseIconSize * scaleRelativeToDragLayer;
final float finalScale = 1f;
float scale = mIsOpening ? initialScale : finalScale;
mFolder.setPivotX(0);
@@ -198,11 +200,12 @@
// Initialize the Folder items' text.
PropertyResetListener colorResetListener =
new PropertyResetListener<>(TEXT_ALPHA_PROPERTY, 1f);
- for (BubbleTextView icon : mFolder.getItemsOnPage(mFolder.mContent.getCurrentPage())) {
+ for (View icon : mFolder.getItemsOnPage(mFolder.mContent.getCurrentPage())) {
+ BubbleTextView titleText = getBubbleTextView(icon);
if (mIsOpening) {
- icon.setTextVisibility(false);
+ titleText.setTextVisibility(false);
}
- ObjectAnimator anim = icon.createTextAlphaAnimator(mIsOpening);
+ ObjectAnimator anim = titleText.createTextAlphaAnimator(mIsOpening);
anim.addListener(colorResetListener);
play(a, anim);
}
@@ -231,23 +234,38 @@
}
play(a, getAnimator(mFolder.mFooter, ALPHA, 0, 1f), footerStartDelay, footerAlphaDuration);
+ ShapeDelegate shapeDelegate = IconShape.INSTANCE.get(mContext).getShape();
// Create reveal animator for the folder background
- play(a, getShape().createRevealAnimator(
+ play(a, shapeDelegate.createRevealAnimator(
mFolder, startRect, endRect, finalRadius, !mIsOpening));
// Create reveal animator for the folder content (capture the top 4 icons 2x2)
int width = mDeviceProfile.folderCellLayoutBorderSpacePx.x
+ mDeviceProfile.folderCellWidthPx * 2;
+ int rtlExtraWidth = 0;
int height = mDeviceProfile.folderCellLayoutBorderSpacePx.y
+ mDeviceProfile.folderCellHeightPx * 2;
int page = mIsOpening ? mContent.getCurrentPage() : mContent.getDestinationPage();
+ // In RTL we want to move to the last 2 columns of icons in the folder.
+ if (Utilities.isRtl(mContext.getResources())) {
+ page = (mContent.getPageCount() - 1) - page;
+ CellLayout clAtPage = mContent.getPageAt(page);
+ if (clAtPage != null) {
+ int numExtraRows = clAtPage.getCountX() - 2;
+ rtlExtraWidth = (int) Math.max(numExtraRows * (mDeviceProfile.folderCellWidthPx
+ + mDeviceProfile.folderCellLayoutBorderSpacePx.x), rtlExtraWidth);
+ }
+ }
int left = mContent.getPaddingLeft() + page * lp.width;
- Rect contentStart = new Rect(left, 0, left + width, height);
+ Rect contentStart = new Rect(
+ left + rtlExtraWidth,
+ 0,
+ left + width + mContent.getPaddingRight() + rtlExtraWidth,
+ height);
Rect contentEnd = new Rect(left, 0, left + lp.width, lp.height);
- play(a, getShape().createRevealAnimator(
+ play(a, shapeDelegate.createRevealAnimator(
mFolder.getContent(), contentStart, contentEnd, finalRadius, !mIsOpening));
-
// Fade in the folder name, as the text can overlap the icons when grid size is small.
mFolder.mFolderName.setAlpha(mIsOpening ? 0f : 1f);
play(a, getAnimator(mFolder.mFolderName, View.ALPHA, 0, 1),
@@ -339,7 +357,7 @@
/**
* Returns the list of "preview items" on {@param page}.
*/
- private List<BubbleTextView> getPreviewIconsOnPage(int page) {
+ private List<View> getPreviewIconsOnPage(int page) {
return mPreviewVerifier.setFolderInfo(mFolder.mInfo)
.previewItemsForPage(page, mFolder.getIconsInReadingOrder());
}
@@ -351,7 +369,7 @@
int previewItemOffsetX, int previewItemOffsetY) {
ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0;
- final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(
+ final List<View> itemsInPreview = getPreviewIconsOnPage(
isOnFirstPage ? 0 : mFolder.mContent.getCurrentPage());
final int numItemsInPreview = itemsInPreview.size();
final int numItemsInFirstPagePreview = isOnFirstPage
@@ -361,48 +379,49 @@
ShortcutAndWidgetContainer cwc = mContent.getPageAt(0).getShortcutsAndWidgets();
for (int i = 0; i < numItemsInPreview; ++i) {
- final BubbleTextView btv = itemsInPreview.get(i);
- CellLayoutLayoutParams btvLp = (CellLayoutLayoutParams) btv.getLayoutParams();
+ final View v = itemsInPreview.get(i);
+ CellLayoutLayoutParams vLp = (CellLayoutLayoutParams) v.getLayoutParams();
// Calculate the final values in the LayoutParams.
- btvLp.isLockedToGrid = true;
- cwc.setupLp(btv);
+ vLp.isLockedToGrid = true;
+ cwc.setupLp(v);
// Match scale of icons in the preview of the items on the first page.
float previewScale = rule.scaleForItem(numItemsInFirstPagePreview);
float previewSize = rule.getIconSize() * previewScale;
- float iconScale = previewSize / itemsInPreview.get(i).getIconSize();
+ float baseIconSize = getBubbleTextView(v).getIconSize();
+ float iconScale = previewSize / baseIconSize;
final float initialScale = iconScale / folderScale;
final float finalScale = 1f;
float scale = mIsOpening ? initialScale : finalScale;
- btv.setScaleX(scale);
- btv.setScaleY(scale);
+ v.setScaleX(scale);
+ v.setScaleY(scale);
// Match positions of the icons in the folder with their positions in the preview
rule.computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, mTmpParams);
// The PreviewLayoutRule assumes that the icon size takes up the entire width so we
// offset by the actual size.
- int iconOffsetX = (int) ((btvLp.width - btv.getIconSize()) * iconScale) / 2;
+ int iconOffsetX = (int) ((vLp.width - baseIconSize) * iconScale) / 2;
final int previewPosX =
(int) ((mTmpParams.transX - iconOffsetX + previewItemOffsetX) / folderScale);
- final float paddingTop = btv.getPaddingTop() * iconScale;
+ final float paddingTop = v.getPaddingTop() * iconScale;
final int previewPosY = (int) ((mTmpParams.transY + previewItemOffsetY - paddingTop)
/ folderScale);
- final float xDistance = previewPosX - btvLp.x;
- final float yDistance = previewPosY - btvLp.y;
+ final float xDistance = previewPosX - vLp.x;
+ final float yDistance = previewPosY - vLp.y;
- Animator translationX = getAnimator(btv, View.TRANSLATION_X, xDistance, 0f);
+ Animator translationX = getAnimator(v, View.TRANSLATION_X, xDistance, 0f);
translationX.setInterpolator(previewItemInterpolator);
play(animatorSet, translationX);
- Animator translationY = getAnimator(btv, View.TRANSLATION_Y, yDistance, 0f);
+ Animator translationY = getAnimator(v, View.TRANSLATION_Y, yDistance, 0f);
translationY.setInterpolator(previewItemInterpolator);
play(animatorSet, translationY);
- Animator scaleAnimator = getAnimator(btv, SCALE_PROPERTY, initialScale, finalScale);
+ Animator scaleAnimator = getAnimator(v, SCALE_PROPERTY, initialScale, finalScale);
scaleAnimator.setInterpolator(previewItemInterpolator);
play(animatorSet, scaleAnimator);
@@ -426,20 +445,20 @@
super.onAnimationStart(animation);
// Necessary to initialize values here because of the start delay.
if (mIsOpening) {
- btv.setTranslationX(xDistance);
- btv.setTranslationY(yDistance);
- btv.setScaleX(initialScale);
- btv.setScaleY(initialScale);
+ v.setTranslationX(xDistance);
+ v.setTranslationY(yDistance);
+ v.setScaleX(initialScale);
+ v.setScaleY(initialScale);
}
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- btv.setTranslationX(0.0f);
- btv.setTranslationY(0.0f);
- btv.setScaleX(1f);
- btv.setScaleY(1f);
+ v.setTranslationX(0.0f);
+ v.setTranslationY(0.0f);
+ v.setScaleX(1f);
+ v.setScaleY(1f);
}
});
}
@@ -482,4 +501,15 @@
? ObjectAnimator.ofArgb(drawable, property, v1, v2)
: ObjectAnimator.ofArgb(drawable, property, v2, v1);
}
+
+ /**
+ * Gets the {@link com.android.launcher3.BubbleTextView} from an icon. In some cases the
+ * BubbleTextView is the whole icon itself, while in others it is contained within the view and
+ * only serves to store the title text.
+ */
+ private BubbleTextView getBubbleTextView(View v) {
+ return v instanceof AppPairIcon
+ ? ((AppPairIcon) v).getTitleTextView()
+ : (BubbleTextView) v;
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java
index cc24761..593673d 100644
--- a/src/com/android/launcher3/folder/FolderGridOrganizer.java
+++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java
@@ -57,7 +57,7 @@
* Updates the organizer with the provided folder info
*/
public FolderGridOrganizer setFolderInfo(FolderInfo info) {
- return setContentSize(info.contents.size());
+ return setContentSize(info.getContents().size());
}
/**
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index ee0d5fc..00636a3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -71,6 +71,7 @@
import com.android.launcher3.logger.LauncherAtom.ToState;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.FolderInfo.FolderListener;
import com.android.launcher3.model.data.FolderInfo.LabelState;
@@ -111,14 +112,14 @@
@Thunk BubbleTextView mFolderName;
- PreviewBackground mBackground = new PreviewBackground();
+ PreviewBackground mBackground = new PreviewBackground(getContext());
private boolean mBackgroundIsVisible = true;
FolderGridOrganizer mPreviewVerifier;
ClippedFolderIconLayoutRule mPreviewLayoutRule;
private PreviewItemManager mPreviewItemManager;
private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0);
- private List<WorkspaceItemInfo> mCurrentPreviewItems = new ArrayList<>();
+ private List<ItemInfo> mCurrentPreviewItems = new ArrayList<>();
boolean mAnimating = false;
@@ -215,7 +216,7 @@
// Keep the notification dot up to date with the sum of all the content's dots.
FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (WorkspaceItemInfo si : folderInfo.contents) {
+ for (ItemInfo si : folderInfo.getContents()) {
folderDotInfo.addDotInfo(activity.getDotInfoForItem(si));
}
icon.setDotInfo(folderDotInfo);
@@ -261,20 +262,18 @@
private boolean willAcceptItem(ItemInfo item) {
final int itemType = item.itemType;
- return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
- itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
- item != mInfo && !mFolder.isOpen());
+ return (Folder.willAcceptItemType(itemType) && item != mInfo && !mFolder.isOpen());
}
public boolean acceptDrop(ItemInfo dragInfo) {
return !mFolder.isDestroyed() && willAcceptItem(dragInfo);
}
- public void addItem(WorkspaceItemInfo item) {
+ public void addItem(ItemInfo item) {
mInfo.add(item, true);
}
- public void removeItem(WorkspaceItemInfo item, boolean animate) {
+ public void removeItem(ItemInfo item, boolean animate) {
mInfo.remove(item, animate);
}
@@ -287,8 +286,8 @@
mOpenAlarm.setOnAlarmListener(mOnOpenListener);
if (SPRING_LOADING_ENABLED &&
((dragInfo instanceof WorkspaceItemFactory)
- || (dragInfo instanceof WorkspaceItemInfo)
- || (dragInfo instanceof PendingAddShortcutInfo))) {
+ || (dragInfo instanceof PendingAddShortcutInfo)
+ || Folder.willAccept(dragInfo))) {
mOpenAlarm.setAlarm(ON_OPEN_DELAY);
}
}
@@ -303,8 +302,8 @@
return mPreviewItemManager.prepareCreateAnimation(destView);
}
- public void performCreateAnimation(final WorkspaceItemInfo destInfo, final View destView,
- final WorkspaceItemInfo srcInfo, final DragObject d, Rect dstRect,
+ public void performCreateAnimation(final ItemInfo destInfo, final View destView,
+ final ItemInfo srcInfo, final DragObject d, Rect dstRect,
float scaleRelativeToDragLayer) {
final DragView srcView = d.dragView;
prepareCreateAnimation(destView);
@@ -330,7 +329,7 @@
mOpenAlarm.cancelAlarm();
}
- private void onDrop(final WorkspaceItemInfo item, DragObject d, Rect finalRect,
+ private void onDrop(final ItemInfo item, DragObject d, Rect finalRect,
float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) {
item.cellX = -1;
item.cellY = -1;
@@ -361,7 +360,7 @@
int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1);
boolean itemAdded = false;
if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) {
- List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
+ List<ItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
mInfo.add(item, index, false);
mCurrentPreviewItems.clear();
mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
@@ -422,7 +421,7 @@
FolderNameInfos nameInfos = new FolderNameInfos();
Executors.MODEL_EXECUTOR.post(() -> {
d.folderNameProvider.getSuggestedFolderName(
- getContext(), mInfo.contents, nameInfos);
+ getContext(), mInfo.getAppContents(), nameInfos);
postDelayed(() -> {
setLabelSuggestion(nameInfos, d.logInstanceId);
invalidate();
@@ -475,19 +474,25 @@
public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
- WorkspaceItemInfo item;
+ ItemInfo item;
if (d.dragInfo instanceof WorkspaceItemFactory) {
// Came from all apps -- make a copy
item = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(getContext());
} else if (d.dragSource instanceof BaseItemDragListener){
// Came from a different window -- make a copy
- item = new WorkspaceItemInfo((WorkspaceItemInfo) d.dragInfo);
+ if (d.dragInfo instanceof AppPairInfo) {
+ // dragged item is app pair
+ item = new AppPairInfo((AppPairInfo) d.dragInfo);
+ } else {
+ // dragged item is WorkspaceItemInfo
+ item = new WorkspaceItemInfo((WorkspaceItemInfo) d.dragInfo);
+ }
} else {
- item = (WorkspaceItemInfo) d.dragInfo;
+ item = d.dragInfo;
}
mFolder.notifyDrop();
onDrop(item, d, null, 1.0f,
- itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(),
+ itemReturnedOnFailedDrop ? item.rank : mInfo.getContents().size(),
itemReturnedOnFailedDrop
);
}
@@ -665,8 +670,8 @@
/**
* Returns the list of items which should be visible in the preview
*/
- public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
- return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents);
+ public List<ItemInfo> getPreviewItemsOnPage(int page) {
+ return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.getContents());
}
@Override
@@ -690,12 +695,12 @@
/**
* Updates the preview items which match the provided condition
*/
- public void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
+ public void updatePreviewItems(Predicate<ItemInfo> itemCheck) {
mPreviewItemManager.updatePreviewItems(itemCheck);
}
@Override
- public void onAdd(WorkspaceItemInfo item, int rank) {
+ public void onAdd(ItemInfo item, int rank) {
updatePreviewItems(false);
boolean wasDotted = mDotInfo.hasDot();
mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
@@ -707,7 +712,7 @@
}
@Override
- public void onRemove(List<WorkspaceItemInfo> items) {
+ public void onRemove(List<ItemInfo> items) {
updatePreviewItems(false);
boolean wasDotted = mDotInfo.hasDot();
items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo);
@@ -718,6 +723,7 @@
requestLayout();
}
+ @Override
public void onTitleChanged(CharSequence title) {
mFolderName.setText(title);
setContentDescription(getAccessiblityTitle(title));
@@ -809,7 +815,7 @@
* Returns a formatted accessibility title for folder
*/
public String getAccessiblityTitle(CharSequence title) {
- int size = mInfo.contents.size();
+ int size = mInfo.getContents().size();
if (size < MAX_NUM_ITEMS_IN_PREVIEW) {
return getContext().getString(R.string.folder_name_format_exact, title, size);
} else {
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index bf59594..be5f8f7 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -28,14 +28,15 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.ModelTaskController;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.Preconditions;
@@ -62,7 +63,7 @@
* name edit box can also be used to provide suggestion.
*/
public static final int SUGGEST_MAX = 4;
- protected IntSparseArrayMap<FolderInfo> mFolderInfos;
+ protected IntSparseArrayMap<CollectionInfo> mCollectionInfos;
protected List<AppInfo> mAppInfos;
/**
@@ -79,7 +80,7 @@
}
public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
- IntSparseArrayMap<FolderInfo> folderInfos) {
+ IntSparseArrayMap<CollectionInfo> folderInfos) {
Preconditions.assertWorkerThread();
FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
context.getApplicationContext(), R.string.folder_name_provider_class);
@@ -93,9 +94,9 @@
new FolderNameWorker());
}
- private void load(List<AppInfo> appInfos, IntSparseArrayMap<FolderInfo> folderInfos) {
+ private void load(List<AppInfo> appInfos, IntSparseArrayMap<CollectionInfo> folderInfos) {
mAppInfos = appInfos;
- mFolderInfos = folderInfos;
+ mCollectionInfos = folderInfos;
}
/**
@@ -191,11 +192,12 @@
nameInfos.setLabel(labels.length - 1, label, 1.0f);
}
- private class FolderNameWorker extends BaseModelUpdateTask {
+ private class FolderNameWorker implements ModelUpdateTask {
+
@Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- mFolderInfos = dataModel.folders.clone();
+ public void execute(@NonNull ModelTaskController taskController,
+ @NonNull BgDataModel dataModel, @NonNull AllAppsList apps) {
+ mCollectionInfos = dataModel.collections.clone();
mAppInfos = Arrays.asList(apps.copyData());
}
}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index f2bed92..8eaa0dc 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -41,8 +41,10 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
@@ -148,7 +150,7 @@
/**
* Binds items to the layout.
*/
- public void bindItems(List<WorkspaceItemInfo> items) {
+ public void bindItems(List<ItemInfo> items) {
if (mViewsBound) {
unbindItems();
}
@@ -164,8 +166,11 @@
CellLayout page = (CellLayout) getChildAt(i);
ShortcutAndWidgetContainer container = page.getShortcutsAndWidgets();
for (int j = container.getChildCount() - 1; j >= 0; j--) {
- container.getChildAt(j).setVisibility(View.VISIBLE);
- mViewCache.recycleView(R.layout.folder_application, container.getChildAt(j));
+ View iconView = container.getChildAt(j);
+ iconView.setVisibility(View.VISIBLE);
+ if (iconView instanceof BubbleTextView) {
+ mViewCache.recycleView(R.layout.folder_application, iconView);
+ }
}
page.removeAllViews();
mViewCache.recycleView(R.layout.folder_page, page);
@@ -185,7 +190,7 @@
* Creates and adds an icon corresponding to the provided rank
* @return the created icon
*/
- public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) {
+ public View createAndAddViewForRank(ItemInfo item, int rank) {
View icon = createNewView(item);
if (!mViewsBound) {
return icon;
@@ -200,7 +205,7 @@
* Adds the {@param view} to the layout based on {@param rank} and updated the position
* related attributes. It assumes that {@param item} is already attached to the view.
*/
- public void addViewForRank(View view, WorkspaceItemInfo item, int rank) {
+ public void addViewForRank(View view, ItemInfo item, int rank) {
int pageNo = rank / mOrganizer.getMaxItemsPerPage();
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) view.getLayoutParams();
@@ -209,26 +214,36 @@
}
@SuppressLint("InflateParams")
- public View createNewView(WorkspaceItemInfo item) {
+ public View createNewView(ItemInfo item) {
if (item == null) {
return null;
}
- final BubbleTextView textView = mViewCache.getView(
- R.layout.folder_application, getContext(), null);
- textView.applyFromWorkspaceItem(item);
- textView.setOnClickListener(mFolder.mActivityContext.getItemOnClickListener());
- textView.setOnLongClickListener(mFolder);
- textView.setOnFocusChangeListener(mFocusIndicatorHelper);
- CellLayoutLayoutParams lp = (CellLayoutLayoutParams) textView.getLayoutParams();
+
+ final View icon;
+ if (item instanceof AppPairInfo api) {
+ // TODO (b/332607759): Make view cache work with app pair icons
+ icon = AppPairIcon.inflateIcon(R.layout.folder_app_pair, ActivityContext.lookupContext(
+ getContext()), null , api, BubbleTextView.DISPLAY_FOLDER);
+ } else {
+ icon = mViewCache.getView(R.layout.folder_application, getContext(), null);
+ ((BubbleTextView) icon).applyFromWorkspaceItem((WorkspaceItemInfo) item);
+ }
+
+ icon.setOnClickListener(mFolder.mActivityContext.getItemOnClickListener());
+ icon.setOnLongClickListener(mFolder);
+ icon.setOnFocusChangeListener(mFocusIndicatorHelper);
+
+ CellLayoutLayoutParams lp = (CellLayoutLayoutParams) icon.getLayoutParams();
if (lp == null) {
- textView.setLayoutParams(new CellLayoutLayoutParams(
+ icon.setLayoutParams(new CellLayoutLayoutParams(
item.cellX, item.cellY, item.spanX, item.spanY));
} else {
lp.setCellX(item.cellX);
lp.setCellY(item.cellY);
lp.cellHSpan = lp.cellVSpan = 1;
}
- return textView;
+
+ return icon;
}
@Nullable
@@ -497,13 +512,20 @@
if (page != null) {
ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets();
for (int i = parent.getChildCount() - 1; i >= 0; i--) {
- BubbleTextView icon = ((BubbleTextView) parent.getChildAt(i));
- icon.verifyHighRes();
+ View iconView = parent.getChildAt(i);
+ Drawable d = null;
+ if (iconView instanceof BubbleTextView btv) {
+ btv.verifyHighRes();
+ d = btv.getIcon();
+ } else if (iconView instanceof AppPairIcon api) {
+ api.verifyHighRes();
+ d = api.getIconDrawableArea().getDrawable();
+ }
+
// Set the callback back to the actual icon, in case
// it was captured by the FolderIcon
- Drawable d = icon.getIcon();
if (d != null) {
- d.setCallback(icon);
+ d.setCallback(iconView);
}
}
}
diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java
index 78298b3..07215c4 100644
--- a/src/com/android/launcher3/folder/LauncherDelegate.java
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -33,7 +33,7 @@
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -86,14 +86,14 @@
FolderInfo info = folder.mInfo;
if (itemCount <= 1) {
View newIcon = null;
- WorkspaceItemInfo finalItem = null;
+ ItemInfo finalItem = null;
if (itemCount == 1) {
// Move the item from the folder to the workspace, in the position of the
// folder
CellLayout cellLayout = mLauncher.getCellLayout(info.container,
mLauncher.getCellPosMapper().mapModelToPresenter(info).screenId);
- finalItem = info.contents.remove(0);
+ finalItem = info.getContents().remove(0);
newIcon = mLauncher.getItemInflater().inflateItem(
finalItem, mLauncher.getModelWriter(), cellLayout);
mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index ec03803..df41d47 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -19,7 +19,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.graphics.IconShape.getShape;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.Animator;
@@ -49,6 +48,8 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.celllayout.DelegatedCellDrawing;
+import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.graphics.IconShape.ShapeDelegate;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
@@ -66,6 +67,7 @@
@VisibleForTesting protected static final float HOVER_SCALE = 1.1f;
@VisibleForTesting protected static final int HOVER_ANIMATION_DURATION = 300;
+ private final Context mContext;
private final PorterDuffXfermode mShadowPorterDuffXfermode
= new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
private RadialGradient mShadowShader = null;
@@ -138,6 +140,10 @@
}
};
+ public PreviewBackground(Context context) {
+ mContext = context;
+ }
+
/**
* Draws folder background under cell layout
*/
@@ -254,6 +260,10 @@
drawShadow(canvas);
}
+ private ShapeDelegate getShape() {
+ return IconShape.INSTANCE.get(mContext).getShape();
+ }
+
public void drawShadow(Canvas canvas) {
if (!DRAW_SHADOW) {
return;
diff --git a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
index 58efdc1..0faa1c9 100644
--- a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
+++ b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
@@ -17,7 +17,7 @@
import android.graphics.drawable.Drawable;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
/**
* Manages the parameters used to draw a Folder preview item.
@@ -30,7 +30,7 @@
public FolderPreviewItemAnim anim;
public boolean hidden;
public Drawable drawable;
- public WorkspaceItemInfo item;
+ public ItemInfo item;
PreviewItemDrawingParams(float transX, float transY, float scale) {
this.transX = transX;
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 9001a0c..6311638 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -16,6 +16,7 @@
package com.android.launcher3.folder;
+import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
@@ -41,7 +42,12 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Utilities;
+import com.android.launcher3.apppairs.AppPairIcon;
+import com.android.launcher3.apppairs.AppPairIconDrawingParams;
+import com.android.launcher3.apppairs.AppPairIconGraphic;
import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.model.data.AppPairInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.Themes;
@@ -125,7 +131,9 @@
}
Drawable prepareCreateAnimation(final View destView) {
- Drawable animateDrawable = ((BubbleTextView) destView).getIcon();
+ Drawable animateDrawable = destView instanceof AppPairIcon
+ ? ((AppPairIcon) destView).getIconDrawableArea().getDrawable()
+ : ((BubbleTextView) destView).getIcon();
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
destView.getMeasuredWidth());
mReferenceDrawable = animateDrawable;
@@ -258,7 +266,7 @@
}
void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
- List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page);
+ List<ItemInfo> items = mIcon.getPreviewItemsOnPage(page);
// We adjust the size of the list to match the number of items in the preview.
while (items.size() < params.size()) {
@@ -328,16 +336,18 @@
mNumOfPrevItems = numOfPrevItemsAux;
}
- void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
+ void updatePreviewItems(Predicate<ItemInfo> itemCheck) {
boolean modified = false;
for (PreviewItemDrawingParams param : mFirstPageParams) {
- if (itemCheck.test(param.item)) {
+ if (itemCheck.test(param.item)
+ || (param.item instanceof AppPairInfo api && api.anyMatch(itemCheck))) {
setDrawable(param, param.item);
modified = true;
}
}
for (PreviewItemDrawingParams param : mCurrentPageParams) {
- if (itemCheck.test(param.item)) {
+ if (itemCheck.test(param.item)
+ || (param.item instanceof AppPairInfo api && api.anyMatch(itemCheck))) {
setDrawable(param, param.item);
modified = true;
}
@@ -370,15 +380,14 @@
* @param newItems The list of items in the new preview.
* @param dropped The item that was dropped onto the FolderIcon.
*/
- public void onDrop(List<WorkspaceItemInfo> oldItems, List<WorkspaceItemInfo> newItems,
- WorkspaceItemInfo dropped) {
+ public void onDrop(List<ItemInfo> oldItems, List<ItemInfo> newItems, ItemInfo dropped) {
int numItems = newItems.size();
final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams;
buildParamsForPage(0, params, false);
// New preview items for items that are moving in (except for the dropped item).
- List<WorkspaceItemInfo> moveIn = new ArrayList<>();
- for (WorkspaceItemInfo newItem : newItems) {
+ List<ItemInfo> moveIn = new ArrayList<>();
+ for (ItemInfo newItem : newItems) {
if (!oldItems.contains(newItem) && !newItem.equals(dropped)) {
moveIn.add(newItem);
}
@@ -401,10 +410,10 @@
}
// Old preview items that need to be moved out.
- List<WorkspaceItemInfo> moveOut = new ArrayList<>(oldItems);
+ List<ItemInfo> moveOut = new ArrayList<>(oldItems);
moveOut.removeAll(newItems);
for (int i = 0; i < moveOut.size(); ++i) {
- WorkspaceItemInfo item = moveOut.get(i);
+ ItemInfo item = moveOut.get(i);
int oldIndex = oldItems.indexOf(item);
PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null);
updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems);
@@ -418,7 +427,7 @@
}
}
- private void updateTransitionParam(final PreviewItemDrawingParams p, WorkspaceItemInfo item,
+ private void updateTransitionParam(final PreviewItemDrawingParams p, ItemInfo item,
int prevIndex, int newIndex, int numItems) {
setDrawable(p, item);
@@ -431,16 +440,24 @@
}
@VisibleForTesting
- public void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
- if (item.hasPromiseIconUi() || (item.runtimeStatusFlags
- & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
- PreloadIconDrawable drawable = newPendingIcon(mContext, item);
- p.drawable = drawable;
- } else {
- p.drawable = item.newIcon(mContext,
- Themes.isThemedIconEnabled(mContext) ? FLAG_THEMED : 0);
+ public void setDrawable(PreviewItemDrawingParams p, ItemInfo item) {
+ if (item instanceof WorkspaceItemInfo wii) {
+ if (wii.hasPromiseIconUi() || (wii.runtimeStatusFlags
+ & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
+ PreloadIconDrawable drawable = newPendingIcon(mContext, wii);
+ p.drawable = drawable;
+ } else {
+ p.drawable = wii.newIcon(mContext,
+ Themes.isThemedIconEnabled(mContext) ? FLAG_THEMED : 0);
+ }
+ p.drawable.setBounds(0, 0, mIconSize, mIconSize);
+ } else if (item instanceof AppPairInfo api) {
+ AppPairIconDrawingParams appPairParams =
+ new AppPairIconDrawingParams(mContext, DISPLAY_FOLDER);
+ p.drawable = AppPairIconGraphic.composeDrawable(api, appPairParams);
+ p.drawable.setBounds(0, 0, mIconSize, mIconSize);
}
- p.drawable.setBounds(0, 0, mIconSize, mIconSize);
+
p.item = item;
// Set the callback to FolderIcon as it is responsible to drawing the icon. The
// callback will be released when the folder is opened.
diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java
index f82b07e..5f8f2dc 100644
--- a/src/com/android/launcher3/graphics/IconShape.java
+++ b/src/com/android/launcher3/graphics/IconShape.java
@@ -22,7 +22,6 @@
import android.animation.FloatArrayEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -35,7 +34,6 @@
import android.graphics.Region.Op;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
-import android.os.Build;
import android.util.AttributeSet;
import android.util.Xml;
import android.view.View;
@@ -45,6 +43,8 @@
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.views.ClipPathView;
import org.xmlpull.v1.XmlPullParser;
@@ -57,38 +57,94 @@
/**
* Abstract representation of the shape of an icon shape
*/
-public abstract class IconShape {
+public final class IconShape implements SafeCloseable {
- private static IconShape sInstance = new Circle();
- private static float sNormalizationScale = ICON_VISIBLE_AREA_FACTOR;
+ public static final MainThreadInitializedObject<IconShape> INSTANCE =
+ new MainThreadInitializedObject<>(IconShape::new);
- public static IconShape getShape() {
- return sInstance;
+
+ private ShapeDelegate mDelegate = new Circle();
+ private float mNormalizationScale = ICON_VISIBLE_AREA_FACTOR;
+
+ private IconShape(Context context) {
+ pickBestShape(context);
}
- public static float getNormalizationScale() {
- return sNormalizationScale;
+ public ShapeDelegate getShape() {
+ return mDelegate;
}
- public boolean enableShapeDetection(){
- return false;
- };
+ public float getNormalizationScale() {
+ return mNormalizationScale;
+ }
- public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
- Paint paint);
+ @Override
+ public void close() { }
- public abstract void addToPath(Path path, float offsetX, float offsetY, float radius);
+ /**
+ * Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
+ */
+ public void pickBestShape(Context context) {
+ // Pick any large size
+ final int size = 200;
- public abstract <T extends View & ClipPathView> Animator createRevealAnimator(T target,
- Rect startRect, Rect endRect, float endRadius, boolean isReversed);
+ Region full = new Region(0, 0, size, size);
+ Region iconR = new Region();
+ AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
+ new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
+ drawable.setBounds(0, 0, size, size);
+ iconR.setPath(drawable.getIconMask(), full);
+
+ Path shapePath = new Path();
+ Region shapeR = new Region();
+
+ // Find the shape with minimum area of divergent region.
+ int minArea = Integer.MAX_VALUE;
+ ShapeDelegate closestShape = null;
+ for (ShapeDelegate shape : getAllShapes(context)) {
+ shapePath.reset();
+ shape.addToPath(shapePath, 0, 0, size / 2f);
+ shapeR.setPath(shapePath, full);
+ shapeR.op(iconR, Op.XOR);
+
+ int area = GraphicsUtils.getArea(shapeR);
+ if (area < minArea) {
+ minArea = area;
+ closestShape = shape;
+ }
+ }
+
+ if (closestShape != null) {
+ mDelegate = closestShape;
+ }
+
+ // Initialize shape properties
+ mNormalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null);
+ }
+
+
+
+ public interface ShapeDelegate {
+
+ default boolean enableShapeDetection() {
+ return false;
+ }
+
+ void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint paint);
+
+ void addToPath(Path path, float offsetX, float offsetY, float radius);
+
+ <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
+ Rect startRect, Rect endRect, float endRadius, boolean isReversed);
+ }
/**
* Abstract shape where the reveal animation is a derivative of a round rect animation
*/
- private static abstract class SimpleRectShape extends IconShape {
+ private static abstract class SimpleRectShape implements ShapeDelegate {
@Override
- public final <T extends View & ClipPathView> Animator createRevealAnimator(T target,
+ public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
return new RoundedRectRevealOutlineProvider(
getStartRadius(startRect), endRadius, startRect, endRect) {
@@ -105,7 +161,7 @@
/**
* Abstract shape which draws using {@link Path}
*/
- private static abstract class PathShape extends IconShape {
+ private static abstract class PathShape implements ShapeDelegate {
private final Path mTmpPath = new Path();
@@ -121,7 +177,7 @@
Rect startRect, Rect endRect, float endRadius, Path outPath);
@Override
- public final <T extends View & ClipPathView> Animator createRevealAnimator(T target,
+ public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
Path path = new Path();
AnimatorUpdateListener listener =
@@ -203,7 +259,7 @@
}
}
- public static class RoundedSquare extends SimpleRectShape {
+ private static class RoundedSquare extends SimpleRectShape {
/**
* Ratio of corner radius to half size.
@@ -237,7 +293,7 @@
}
}
- public static class TearDrop extends PathShape {
+ private static class TearDrop extends PathShape {
/**
* Radio of short radius to large radius, based on the shape options defined in the config.
@@ -289,7 +345,7 @@
}
}
- public static class Squircle extends PathShape {
+ private static class Squircle extends PathShape {
/**
* Radio of radius to circle radius, based on the shape options defined in the config.
@@ -375,14 +431,7 @@
}
}
- /**
- * Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
- */
- public static void init(Context context) {
- pickBestShape(context);
- }
-
- private static IconShape getShapeDefinition(String type, float radius) {
+ private static ShapeDelegate getShapeDefinition(String type, float radius) {
switch (type) {
case "Circle":
return new Circle();
@@ -397,8 +446,8 @@
}
}
- private static List<IconShape> getAllShapes(Context context) {
- ArrayList<IconShape> result = new ArrayList<>();
+ private static List<ShapeDelegate> getAllShapes(Context context) {
+ ArrayList<ShapeDelegate> result = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.folder_shapes)) {
// Find the root tag
@@ -416,7 +465,7 @@
if (type == XmlPullParser.START_TAG) {
AttributeSet attrs = Xml.asAttributeSet(parser);
TypedArray a = context.obtainStyledAttributes(attrs, radiusAttr);
- IconShape shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
+ ShapeDelegate shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
a.recycle();
result.add(shape);
@@ -428,42 +477,4 @@
return result;
}
- @TargetApi(Build.VERSION_CODES.O)
- protected static void pickBestShape(Context context) {
- // Pick any large size
- final int size = 200;
-
- Region full = new Region(0, 0, size, size);
- Region iconR = new Region();
- AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
- new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
- drawable.setBounds(0, 0, size, size);
- iconR.setPath(drawable.getIconMask(), full);
-
- Path shapePath = new Path();
- Region shapeR = new Region();
-
- // Find the shape with minimum area of divergent region.
- int minArea = Integer.MAX_VALUE;
- IconShape closestShape = null;
- for (IconShape shape : getAllShapes(context)) {
- shapePath.reset();
- shape.addToPath(shapePath, 0, 0, size / 2f);
- shapeR.setPath(shapePath, full);
- shapeR.op(iconR, Op.XOR);
-
- int area = GraphicsUtils.getArea(shapeR);
- if (area < minArea) {
- minArea = area;
- closestShape = shape;
- }
- }
-
- if (closestShape != null) {
- sInstance = closestShape;
- }
-
- // Initialize shape properties
- sNormalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null);
- }
}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index e0a6627..ae8f1d5 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -20,9 +20,11 @@
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.View.VISIBLE;
+import static com.android.launcher3.BubbleTextView.DISPLAY_TASKBAR;
+import static com.android.launcher3.BubbleTextView.DISPLAY_WORKSPACE;
import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
+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;
@@ -64,7 +66,6 @@
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.R;
import com.android.launcher3.Utilities;
@@ -75,19 +76,16 @@
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.AppPairInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.FolderInfo;
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.pm.UserCache;
-import com.android.launcher3.uioverrides.PredictedAppIconInflater;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.IntArray;
@@ -101,7 +99,6 @@
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.launcher3.widget.LocalColorExtractor;
-import com.android.launcher3.widget.custom.CustomWidgetManager;
import com.android.launcher3.widget.util.WidgetSizes;
import java.util.ArrayList;
@@ -109,7 +106,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -128,47 +124,12 @@
*/
public static class PreviewContext extends SandboxContext {
- private final InvariantDeviceProfile mIdp;
- private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
- new ConcurrentLinkedQueue<>();
-
public PreviewContext(Context base, InvariantDeviceProfile idp) {
- super(base, UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
- LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
- CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE,
- WindowManagerProxy.INSTANCE, DisplayController.INSTANCE);
- mIdp = idp;
- mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp);
- mObjectMap.put(LauncherAppState.INSTANCE,
+ super(base);
+ putObject(InvariantDeviceProfile.INSTANCE, idp);
+ putObject(LauncherAppState.INSTANCE,
new LauncherAppState(this, null /* iconCacheFileName */));
}
-
- /**
- * Creates a new LauncherIcons for the preview, skipping the global pool
- */
- public LauncherIcons newLauncherIcons(Context context) {
- LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll();
- if (launcherIconsForPreview != null) {
- return launcherIconsForPreview;
- }
- return new LauncherIconsForPreview(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize,
- -1 /* poolId */);
- }
-
- private final class LauncherIconsForPreview extends LauncherIcons {
-
- private LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize,
- int poolId) {
- super(context, fillResIconDpi, iconBitmapSize, poolId);
- }
-
- @Override
- public void recycle() {
- // Clear any temporary state variables
- clear();
- mIconPool.offer(this);
- }
- }
}
private final List<OnDeviceProfileChangeListener> mDpChangeListeners = new ArrayList<>();
@@ -226,10 +187,11 @@
launcherWidgetSpanInfo;
CellLayout firstScreen = mRootView.findViewById(R.id.workspace);
- firstScreen.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingPx.left,
+ firstScreen.setPadding(
+ mDp.workspacePadding.left + mDp.cellLayoutPaddingPx.left,
mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top,
- (mDp.isTwoPanels ? mDp.cellLayoutBorderSpacePx.x / 2
- : mDp.workspacePadding.right) + mDp.cellLayoutPaddingPx.right,
+ mDp.isTwoPanels ? (mDp.cellLayoutBorderSpacePx.x / 2)
+ : (mDp.workspacePadding.right + mDp.cellLayoutPaddingPx.right),
mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom
);
mWorkspaceScreens.put(FIRST_SCREEN_ID, firstScreen);
@@ -237,7 +199,7 @@
if (mDp.isTwoPanels) {
CellLayout rightPanel = mRootView.findViewById(R.id.workspace_right);
rightPanel.setPadding(
- mDp.cellLayoutBorderSpacePx.x / 2 + mDp.cellLayoutPaddingPx.left,
+ mDp.cellLayoutBorderSpacePx.x / 2,
mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top,
mDp.workspacePadding.right + mDp.cellLayoutPaddingPx.right,
mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom
@@ -387,14 +349,16 @@
addInScreenFromBind(icon, info);
}
- private void inflateAndAddCollectionIcon(FolderInfo info) {
- CellLayout screen = info.container == Favorites.CONTAINER_DESKTOP
+ private void inflateAndAddCollectionIcon(CollectionInfo info) {
+ boolean isOnDesktop = info.container == Favorites.CONTAINER_DESKTOP;
+ CellLayout screen = isOnDesktop
? mWorkspaceScreens.get(info.screenId)
: mHotseat;
- FrameLayout folderIcon = info.itemType == Favorites.ITEM_TYPE_FOLDER
- ? FolderIcon.inflateIcon(R.layout.folder_icon, this, screen, info)
- : AppPairIcon.inflateIcon(R.layout.app_pair_icon, this, screen, info);
- addInScreenFromBind(folderIcon, info);
+ FrameLayout collectionIcon = info.itemType == Favorites.ITEM_TYPE_FOLDER
+ ? FolderIcon.inflateIcon(R.layout.folder_icon, this, screen, (FolderInfo) info)
+ : AppPairIcon.inflateIcon(R.layout.app_pair_icon, this, screen, (AppPairInfo) info,
+ isOnDesktop ? DISPLAY_WORKSPACE : DISPLAY_TASKBAR);
+ addInScreenFromBind(collectionIcon, info);
}
private void inflateAndAddWidgets(
@@ -414,7 +378,7 @@
private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
- info.providerName, info.user);
+ info.providerName, info.user, mContext);
if (widgetItem == null) {
return;
}
@@ -453,10 +417,10 @@
private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
CellLayout screen = mWorkspaceScreens.get(info.screenId);
- View view = PredictedAppIconInflater.inflate(mHomeElementInflater, screen, info);
- if (view != null) {
- addInScreenFromBind(view, info);
- }
+ BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
+ R.layout.predicted_app_icon, screen, false);
+ icon.applyFromWorkspaceItem(info);
+ addInScreenFromBind(icon, info);
}
private void dispatchVisibilityAggregated(View view, boolean isVisible) {
@@ -498,7 +462,7 @@
break;
case Favorites.ITEM_TYPE_FOLDER:
case Favorites.ITEM_TYPE_APP_PAIR:
- inflateAndAddCollectionIcon((FolderInfo) itemInfo);
+ inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
break;
default:
break;
@@ -541,7 +505,7 @@
// Add first page QSB
if (FeatureFlags.QSB_ON_FIRST_SCREEN && dataModel.isFirstPagePinnedItemEnabled
- && !shouldShowFirstPageWidget()) {
+ && !SHOULD_SHOW_FIRST_PAGE_WIDGET) {
CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen, false);
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 051fb6f..addd072 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -37,7 +37,6 @@
import android.view.SurfaceControlViewHost;
import android.view.SurfaceControlViewHost.SurfacePackage;
import android.view.View;
-import android.view.WindowManager.LayoutParams;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.annotation.NonNull;
@@ -51,10 +50,10 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Workspace;
import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
+import com.android.launcher3.model.BaseLauncherBinder;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.GridSizeMigrationUtil;
-import com.android.launcher3.model.LauncherBinder;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.provider.LauncherDbUtils;
@@ -87,6 +86,7 @@
private final int mHeight;
private String mGridName;
+ private final int mDisplayId;
private final Display mDisplay;
private final WallpaperColors mWallpaperColors;
private final RunnableList mOnDestroyCallbacks = new RunnableList();
@@ -110,8 +110,12 @@
mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
mWidth = bundle.getInt(KEY_VIEW_WIDTH);
mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
+ mDisplayId = bundle.getInt(KEY_DISPLAY_ID);
mDisplay = context.getSystemService(DisplayManager.class)
- .getDisplay(bundle.getInt(KEY_DISPLAY_ID));
+ .getDisplay(mDisplayId);
+ if (mDisplay == null) {
+ throw new IllegalArgumentException("Display ID does not match any displays.");
+ }
mSurfaceControlViewHost = MAIN_EXECUTOR.submit(() ->
new SurfaceControlViewHost(mContext, context.getSystemService(DisplayManager.class)
@@ -121,7 +125,7 @@
}
public int getDisplayId() {
- return mDisplay.getDisplayId();
+ return mDisplayId;
}
public IBinder getHostToken() {
@@ -210,7 +214,6 @@
return new ContextThemeWrapper(context,
Themes.getActivityThemeRes(context));
}
- context = context.createWindowContext(LayoutParams.TYPE_APPLICATION_OVERLAY, null);
LocalColorExtractor.newInstance(context)
.applyColorsOverride(context, mWallpaperColors);
return new ContextThemeWrapper(context,
@@ -241,7 +244,7 @@
/* bgAllAppsList= */ null,
bgModel,
LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(),
- new LauncherBinder(LauncherAppState.getInstance(previewContext), bgModel,
+ new BaseLauncherBinder(LauncherAppState.getInstance(previewContext), bgModel,
/* bgAllAppsList= */ null, new Callbacks[0])) {
@Override
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 8e73660..44e448e 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -51,11 +51,13 @@
import androidx.annotation.VisibleForTesting;
import androidx.core.util.Pair;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -222,6 +224,7 @@
* Updates {@param application} only if a valid entry is found.
*/
public synchronized void updateTitleAndIcon(AppInfo application) {
+ boolean preferPackageIcon = application.isArchived();
CacheEntry entry = cacheLocked(application.componentName,
application.user, () -> null, mLauncherActivityInfoCachingLogic,
false, application.usingLowResIcon());
@@ -229,13 +232,12 @@
return;
}
- boolean preferPackageIcon = application.isArchived();
if (preferPackageIcon) {
String packageName = application.getTargetPackage();
CacheEntry packageEntry =
cacheLocked(new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
application.user, () -> null, mLauncherActivityInfoCachingLogic,
- false, application.usingLowResIcon());
+ true, application.usingLowResIcon());
applyPackageEntry(packageEntry, application, entry);
} else {
applyCacheEntry(entry, application);
@@ -248,7 +250,7 @@
@SuppressWarnings("NewApi")
public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
LauncherActivityInfo activityInfo, boolean useLowResIcon) {
- boolean isAppArchived = Utilities.enableSupportForArchiving() && activityInfo != null
+ boolean isAppArchived = Flags.enableSupportForArchiving() && activityInfo != null
&& activityInfo.getActivityInfo().isArchived;
// If we already have activity info, no need to use package icon
getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon,
@@ -372,8 +374,13 @@
infoInOut.user, activityInfoProvider, mLauncherActivityInfoCachingLogic,
usePkgIcon, useLowResIcon);
applyPackageEntry(packageEntry, infoInOut, entry);
- } else {
+ } else if (useLowResIcon || !entry.bitmap.isNullOrLowRes()
+ || infoInOut.bitmap.isNullOrLowRes()) {
+ // Only use cache entry if it will not downgrade the current bitmap in infoInOut
applyCacheEntry(entry, infoInOut);
+ } else {
+ Log.d(TAG, "getTitleAndIcon: Cache entry bitmap was a downgrade of existing bitmap"
+ + " in ItemInfo. Skipping.");
}
}
@@ -469,17 +476,22 @@
duplicateIconRequestsMap.get(cn);
if (cn != null) {
- CacheEntry entry = cacheLocked(
- cn,
- /* user = */ sectionKey.first,
- () -> duplicateIconRequests.get(0).launcherActivityInfo,
- mLauncherActivityInfoCachingLogic,
- c,
- /* usePackageIcon= */ false,
- /* useLowResIcons = */ sectionKey.second);
+ if (duplicateIconRequests != null) {
+ CacheEntry entry = cacheLocked(
+ cn,
+ /* user = */ sectionKey.first,
+ () -> duplicateIconRequests.get(0).launcherActivityInfo,
+ mLauncherActivityInfoCachingLogic,
+ c,
+ /* usePackageIcon= */ false,
+ /* useLowResIcons = */ sectionKey.second);
- for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
- applyCacheEntry(entry, iconRequest.itemInfo);
+ for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
+ applyCacheEntry(entry, iconRequest.itemInfo);
+ }
+ } else {
+ Log.e(TAG, "Found entry in icon database but no main activity "
+ + "entry for cn: " + cn);
}
}
}
@@ -566,7 +578,7 @@
try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
final BitmapInfo tempBitmap = li.createBadgedIconBitmap(
mContext.getDrawable(widgetSection.mSectionDrawable),
- new BaseIconFactory.IconOptions().setShrinkNonAdaptiveIcons(false));
+ new BaseIconFactory.IconOptions());
mWidgetCategoryBitmapInfos.put(infoInOut.widgetCategory, tempBitmap);
infoInOut.bitmap = getBadgedIcon(tempBitmap, infoInOut.user);
} catch (Exception e) {
@@ -630,4 +642,10 @@
void reapplyItemInfo(ItemInfoWithIcon info);
}
+
+ /** Log persistently to FileLog.d for debugging. */
+ @Override
+ protected void logdPersistently(String tag, String message, @Nullable Exception e) {
+ FileLog.d(tag, message, e);
+ }
}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index a15348b..884d448 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -16,94 +16,69 @@
package com.android.launcher3.icons;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_FORCED_MONO_ICON;
-
import android.content.Context;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import androidx.annotation.NonNull;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.UserIconInfo;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
/**
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
* that are threadsafe.
*/
public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
- private static final Object sPoolSync = new Object();
- private static LauncherIcons sPool;
- private static int sPoolId = 0;
+ private static final MainThreadInitializedObject<Pool> POOL =
+ new MainThreadInitializedObject<>(Pool::new);
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static LauncherIcons obtain(Context context) {
- if (context instanceof LauncherPreviewRenderer.PreviewContext) {
- return ((LauncherPreviewRenderer.PreviewContext) context).newLauncherIcons(context);
- }
-
- int poolId;
- synchronized (sPoolSync) {
- if (sPool != null) {
- LauncherIcons m = sPool;
- sPool = m.next;
- m.next = null;
- return m;
- }
- poolId = sPoolId;
- }
-
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
- return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId);
+ return POOL.get(context).obtain();
}
- public static void clearPool() {
- synchronized (sPoolSync) {
- sPool = null;
- sPoolId++;
- }
+ public static void clearPool(Context context) {
+ POOL.get(context).close();
}
- private final int mPoolId;
-
- private LauncherIcons next;
+ private final ConcurrentLinkedQueue<LauncherIcons> mPool;
private MonochromeIconFactory mMonochromeIconFactory;
- protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
- super(context, fillResIconDpi, iconBitmapSize, IconShape.getShape().enableShapeDetection());
+ protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
+ ConcurrentLinkedQueue<LauncherIcons> pool) {
+ super(context, fillResIconDpi, iconBitmapSize,
+ IconShape.INSTANCE.get(context).getShape().enableShapeDetection());
mMonoIconEnabled = Themes.isThemedIconEnabled(context);
- mPoolId = poolId;
+ mPool = pool;
}
/**
* Recycles a LauncherIcons that may be in-use.
*/
public void recycle() {
- synchronized (sPoolSync) {
- if (sPoolId != mPoolId) {
- return;
- }
- // Clear any temporary state variables
- clear();
-
- next = sPool;
- sPool = this;
- }
+ clear();
+ mPool.add(this);
}
@Override
- protected Drawable getMonochromeDrawable(Drawable base) {
+ protected Drawable getMonochromeDrawable(AdaptiveIconDrawable base) {
Drawable mono = super.getMonochromeDrawable(base);
- if (mono != null || !ENABLE_FORCED_MONO_ICON.get()) {
+ if (mono != null || !Flags.forceMonochromeAppIcons()) {
return mono;
}
if (mMonochromeIconFactory == null) {
@@ -122,4 +97,33 @@
public void close() {
recycle();
}
+
+ private static class Pool implements SafeCloseable {
+
+ private final Context mContext;
+
+ @NonNull
+ private ConcurrentLinkedQueue<LauncherIcons> mPool = new ConcurrentLinkedQueue<>();
+
+ private Pool(Context context) {
+ mContext = context;
+ }
+
+ public LauncherIcons obtain() {
+ ConcurrentLinkedQueue<LauncherIcons> pool = mPool;
+ LauncherIcons m = pool.poll();
+
+ if (m == null) {
+ InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
+ return new LauncherIcons(mContext, idp.fillResIconDpi, idp.iconBitmapSize, pool);
+ } else {
+ return m;
+ }
+ }
+
+ @Override
+ public void close() {
+ mPool = new ConcurrentLinkedQueue<>();
+ }
+ }
}
diff --git a/src/com/android/launcher3/icons/MonochromeIconFactory.java b/src/com/android/launcher3/icons/MonochromeIconFactory.java
index 511dcc7..2854d51 100644
--- a/src/com/android/launcher3/icons/MonochromeIconFactory.java
+++ b/src/com/android/launcher3/icons/MonochromeIconFactory.java
@@ -100,20 +100,12 @@
* Creates a monochrome version of the provided drawable
*/
@WorkerThread
- public Drawable wrap(Drawable icon) {
- if (icon instanceof AdaptiveIconDrawable) {
- AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon;
- mFlatCanvas.drawColor(Color.BLACK);
- drawDrawable(aid.getBackground());
- drawDrawable(aid.getForeground());
- generateMono();
- return new ClippedMonoDrawable(this);
- } else {
- mFlatCanvas.drawColor(Color.WHITE);
- drawDrawable(icon);
- generateMono();
- return this;
- }
+ public Drawable wrap(AdaptiveIconDrawable icon) {
+ mFlatCanvas.drawColor(Color.BLACK);
+ drawDrawable(icon.getBackground());
+ drawDrawable(icon.getForeground());
+ generateMono();
+ return new ClippedMonoDrawable(this);
}
@WorkerThread
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
index 1791539..f40eda6 100644
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
@@ -16,7 +16,7 @@
package com.android.launcher3.icons;
-import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import android.content.ComponentName;
import android.content.Context;
@@ -101,7 +101,7 @@
* Launcher specific checks
*/
public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) {
- if (GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
return null;
}
try {
diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index 3e320bd..72dc63e 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -31,9 +31,11 @@
implements OnFocusChangeListener {
public FocusIndicatorHelper(View container) {
- super(container, Flags.enableFocusOutline() ? Themes.getAttrColor(container.getContext(),
- R.attr.focusOutlineColor)
- : container.getResources().getColor(R.color.focused_background));
+ super(container,
+ Flags.enableFocusOutline() ? new int[]{Themes.getAttrColor(container.getContext(),
+ R.attr.focusOutlineColor), Themes.getAttrColor(container.getContext(),
+ R.attr.focusInnerOutlineColor)}
+ : new int[]{container.getResources().getColor(R.color.focused_background)});
}
@Override
diff --git a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
index a8cd03b..480e8f3 100644
--- a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
@@ -74,7 +74,8 @@
private static final Rect sTempRect2 = new Rect();
private final View mContainer;
- protected final Paint mPaint;
+ protected final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint mInnerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final int mMaxAlpha;
private final Rect mDirtyRect = new Rect();
@@ -93,24 +94,31 @@
private ObjectAnimator mCurrentAnimation;
private float mAlpha;
private float mRadius;
+ private float mInnerRadius;
- public ItemFocusIndicatorHelper(View container, int color) {
+ public ItemFocusIndicatorHelper(View container, int... colors) {
mContainer = container;
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setColor(0xFF000000 | color);
- if (Flags.enableFocusOutline()) {
+ mPaint.setColor(0xFF000000 | colors[0]);
+ if (Flags.enableFocusOutline() && colors.length > 1) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(container.getResources().getDimensionPixelSize(
R.dimen.focus_outline_stroke_width));
mRadius = container.getResources().getDimensionPixelSize(
R.dimen.focus_outline_radius);
+
+ mInnerPaint.setStyle(Paint.Style.STROKE);
+ mInnerPaint.setColor(0xFF000000 | colors[1]);
+ mInnerPaint.setStrokeWidth(container.getResources().getDimensionPixelSize(
+ R.dimen.focus_outline_stroke_width));
+ mInnerRadius = container.getResources().getDimensionPixelSize(
+ R.dimen.focus_inner_outline_radius);
} else {
mPaint.setStyle(Paint.Style.FILL);
mRadius = container.getResources().getDimensionPixelSize(
R.dimen.grid_visualization_rounding_radius);
}
- mMaxAlpha = Color.alpha(color);
+ mMaxAlpha = Color.alpha(colors[0]);
setAlpha(0);
mShift = 0;
@@ -119,6 +127,7 @@
protected void setAlpha(float alpha) {
mAlpha = alpha;
mPaint.setAlpha((int) (mAlpha * mMaxAlpha));
+ mInnerPaint.setAlpha((int) (mAlpha * mMaxAlpha));
}
@Override
@@ -147,11 +156,18 @@
Rect newRect = getDrawRect();
if (newRect != null) {
if (Flags.enableFocusOutline()) {
- // Stroke is drawn with half outside and half inside the view. Inset by half
- // stroke width to move the whole stroke inside the view and avoid other views
- // occluding it
- int halfStrokeWidth = (int) mPaint.getStrokeWidth() / 2;
- newRect.inset(halfStrokeWidth, halfStrokeWidth);
+ int strokeWidth = (int) mPaint.getStrokeWidth();
+ // Inset for inner outline. Stroke is drawn with half outside and half inside
+ // the view. Inset by half stroke width to move the whole stroke inside the view
+ // and avoid other views occluding it. Inset one more stroke width to leave space
+ // for outer outline.
+ newRect.inset((int) (strokeWidth * 1.5), (int) (strokeWidth * 1.5));
+ c.drawRoundRect((float) newRect.left, (float) newRect.top,
+ (float) newRect.right, (float) newRect.bottom,
+ mInnerRadius, mInnerRadius, mInnerPaint);
+
+ // Inset outward for drawing outer outline
+ newRect.inset(-strokeWidth, -strokeWidth);
}
mDirtyRect.set(newRect);
c.drawRoundRect((float) mDirtyRect.left, (float) mDirtyRect.top,
@@ -230,7 +246,8 @@
protected void setCurrentItem(T item) {
mCurrentItem = item;
- mShift = 0;
+ // Set it to end value directly to skip the animation for outline
+ mShift = Flags.enableFocusOutline() ? 1 : 0;
mTargetItem = null;
}
diff --git a/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java b/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java
index f9bd343..21d157a 100644
--- a/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java
+++ b/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.view.View;
+import com.android.launcher3.Flags;
import com.android.launcher3.PagedView;
/**
@@ -27,6 +28,7 @@
public class ViewGroupFocusHelper extends FocusIndicatorHelper {
private final View mContainer;
+ private static final Rect sTempRect = new Rect();
public ViewGroupFocusHelper(View container) {
super(container);
@@ -34,19 +36,45 @@
}
@Override
+ protected boolean shouldDraw(View item) {
+ if (Flags.enableFocusOutline()) {
+ // Not draw outline in page transition because the outline just remains fully
+ // persistent during the transition and does not look smooth
+ return super.shouldDraw(item) && !isInPageTransition(item);
+ } else {
+ return super.shouldDraw(item);
+ }
+ }
+
+ private boolean isInPageTransition(View view) {
+ if (view == null || !(view.getParent() instanceof View)) {
+ return false;
+ }
+ boolean isInTransition = false;
+ if (view instanceof PagedView) {
+ isInTransition = ((PagedView<?>) view).isPageInTransition();
+ }
+ return isInTransition || isInPageTransition((View) view.getParent());
+ }
+
+ @Override
public void viewToRect(View v, Rect outRect) {
- outRect.left = 0;
- outRect.top = 0;
+ // Using FocusedRect here allows views to provide their custom rect for drawing outline,
+ // e.g. making the Rect bigger than the content to leave some padding between view and
+ // outline
+ v.getFocusedRect(sTempRect);
+ outRect.left = sTempRect.left;
+ outRect.top = sTempRect.top;
computeLocationRelativeToContainer(v, outRect);
// If a view is scaled, its position will also shift accordingly. For optimization, only
// consider this for the last node.
- outRect.left += (1 - v.getScaleX()) * v.getWidth() / 2;
- outRect.top += (1 - v.getScaleY()) * v.getHeight() / 2;
+ outRect.left = (int) (outRect.left + (1 - v.getScaleX()) * sTempRect.width() / 2);
+ outRect.top = (int) (outRect.top + (1 - v.getScaleY()) * sTempRect.height() / 2);
- outRect.right = outRect.left + (int) (v.getScaleX() * v.getWidth());
- outRect.bottom = outRect.top + (int) (v.getScaleY() * v.getHeight());
+ outRect.right = outRect.left + (int) (v.getScaleX() * sTempRect.width());
+ outRect.bottom = outRect.top + (int) (v.getScaleY() * sTempRect.height());
}
private void computeLocationRelativeToContainer(View child, Rect outRect) {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 5cb1540..861631d 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.slice.SliceItem;
@@ -53,11 +54,18 @@
public static final int LAUNCHER_STATE_ALLAPPS = 4;
public static final int LAUNCHER_STATE_UNCHANGED = 5;
+ @NonNull
+ protected final Context mContext;
+ @Nullable
+ protected final ActivityContext mActivityContext;
+
+ private KeyboardStateManager mKeyboardStateManager;
private InstanceId mInstanceId;
- protected @Nullable ActivityContext mActivityContext = null;
- protected @Nullable Context mContext = null;
- private KeyboardStateManager mKeyboardStateManager;
+ public StatsLogManager(@NonNull Context context) {
+ mContext = context;
+ mActivityContext = ActivityContext.lookupContextNoThrow(context);
+ }
/**
* Returns event enum based on the two state transition information when swipe
@@ -210,6 +218,9 @@
@UiEvent(doc = "User tapped on free form icon on a task menu.")
LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP(519),
+ @UiEvent(doc = "User tapped on desktop icon on a task menu.")
+ LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP(1706),
+
@UiEvent(doc = "User tapped on pause app system shortcut.")
LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
@@ -751,6 +762,39 @@
+ " metric.")
LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED(1612),
+ @UiEvent(doc = "User tapped add widget button in widget sheet.")
+ LAUNCHER_WIDGET_ADD_BUTTON_TAP(1622),
+
+ @UiEvent(doc = "Number of user installed Private profile apps, shown above separator line")
+ LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT(1672),
+
+ @UiEvent(doc = "Number of preinstalled Private profile apps, shown under separator line")
+ LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT(1673),
+
+ @UiEvent(doc = "Private space lock animation started")
+ LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN(1725),
+
+ @UiEvent(doc = "Private space lock animation finished")
+ LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END(1726),
+
+ @UiEvent(doc = "Private space unlock animation started")
+ LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN(1727),
+
+ @UiEvent(doc = "Private space unlock animation finished")
+ LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END(1728),
+
+ @UiEvent(doc = "User rotates whilst in Overview / RecentsView")
+ LAUNCHER_OVERVIEW_ORIENTATION_CHANGED(1762),
+
+ @UiEvent(doc = "User launches Overview from 3 button navigation")
+ LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON(1763),
+
+ @UiEvent(doc = "User launches Overview from alt+tab keyboard quick switch")
+ LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH(1764),
+
+ @UiEvent(doc = "User launches Overview from meta+tab keyboard shortcut")
+ LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT(1765),
+
// ADD MORE
;
@@ -1185,10 +1229,7 @@
* Creates a new instance of {@link StatsLogManager} based on provided context.
*/
public static StatsLogManager newInstance(Context context) {
- StatsLogManager manager = Overrides.getObject(StatsLogManager.class,
- context.getApplicationContext(), R.string.stats_log_manager_class);
- manager.mActivityContext = ActivityContext.lookupContextNoThrow(context);
- manager.mContext = context;
- return manager;
+ return Overrides.getObject(
+ StatsLogManager.class, context, R.string.stats_log_manager_class);
}
}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 96a8da9..427fb97 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.model;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
@@ -25,13 +26,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -49,7 +51,7 @@
/**
* Task to add auto-created workspace items.
*/
-public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
+public class AddWorkspaceItemsTask implements ModelUpdateTask {
private static final String LOG = "AddWorkspaceItemsTask";
@@ -76,15 +78,17 @@
mItemSpaceFinder = itemSpaceFinder;
}
+
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
if (mItemList.isEmpty()) {
return;
}
final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
final IntArray addedWorkspaceScreensFinal = new IntArray();
+ final Context context = taskController.getApp().getContext();
synchronized (dataModel) {
IntArray workspaceScreens = dataModel.collectWorkspaceScreens();
@@ -99,7 +103,7 @@
}
// b/139663018 Short-circuit this logic if the icon is a system app
- if (PackageManagerHelper.isSystemApp(app.getContext(),
+ if (PackageManagerHelper.isSystemApp(context,
Objects.requireNonNull(item.getIntent()))) {
continue;
}
@@ -112,7 +116,7 @@
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
if (item instanceof WorkspaceItemFactory) {
- item = ((WorkspaceItemFactory) item).makeWorkspaceItem(app.getContext());
+ item = ((WorkspaceItemFactory) item).makeWorkspaceItem(context);
}
}
if (item != null) {
@@ -121,21 +125,21 @@
}
InstallSessionHelper packageInstaller =
- InstallSessionHelper.INSTANCE.get(app.getContext());
- LauncherApps launcherApps = app.getContext().getSystemService(LauncherApps.class);
+ InstallSessionHelper.INSTANCE.get(context);
+ LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
for (ItemInfo item : filteredItems) {
// Find appropriate space for the item.
- int[] coords = mItemSpaceFinder.findSpaceForItem(app, dataModel, workspaceScreens,
- addedWorkspaceScreensFinal, item.spanX, item.spanY);
+ int[] coords = mItemSpaceFinder.findSpaceForItem(taskController.getApp(), dataModel,
+ workspaceScreens, addedWorkspaceScreensFinal, item.spanX, item.spanY);
int screenId = coords[0];
ItemInfo itemInfo;
- if (item instanceof WorkspaceItemInfo || item instanceof FolderInfo ||
- item instanceof LauncherAppWidgetInfo) {
+ if (item instanceof WorkspaceItemInfo || item instanceof CollectionInfo
+ || item instanceof LauncherAppWidgetInfo) {
itemInfo = item;
} else if (item instanceof WorkspaceItemFactory) {
- itemInfo = ((WorkspaceItemFactory) item).makeWorkspaceItem(app.getContext());
+ itemInfo = ((WorkspaceItemFactory) item).makeWorkspaceItem(context);
} else {
throw new RuntimeException("Unexpected info type");
}
@@ -174,8 +178,8 @@
if (hasActivity) {
// App was installed while launcher was in the background,
// or app was already installed for another user.
- itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user)
- .makeWorkspaceItem(app.getContext());
+ itemInfo = new AppInfo(context, activities.get(0), item.user)
+ .makeWorkspaceItem(context);
if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) {
// We need this additional check here since we treat all auto added
@@ -185,16 +189,17 @@
continue;
}
+ IconCache cache = taskController.getApp().getIconCache();
WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
wii.title = "";
- wii.bitmap = app.getIconCache().getDefaultIcon(item.user);
- app.getIconCache().getTitleAndIcon(wii,
+ wii.bitmap = cache.getDefaultIcon(item.user);
+ cache.getTitleAndIcon(wii,
((WorkspaceItemInfo) itemInfo).usingLowResIcon());
}
}
// Add the shortcut to the db
- getModelWriter().addItemToDatabase(itemInfo,
+ taskController.getModelWriter().addItemToDatabase(itemInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId,
coords[1], coords[2]);
@@ -207,7 +212,7 @@
}
if (!addedItemsFinal.isEmpty()) {
- scheduleCallbackTask(new CallbackTask() {
+ taskController.scheduleCallbackTask(new CallbackTask() {
@Override
public void execute(@NonNull Callbacks callbacks) {
final ArrayList<ItemInfo> addAnimated = new ArrayList<>();
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 8659471..64ebbf3 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -18,7 +18,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_ARCHIVED;
import android.content.ComponentName;
import android.content.Context;
@@ -33,13 +32,14 @@
import androidx.annotation.Nullable;
import com.android.launcher3.AppFilter;
-import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SafeCloseable;
@@ -60,7 +60,7 @@
private static final String TAG = "AllAppsList";
private static final Consumer<AppInfo> NO_OP_CONSUMER = a -> { };
-
+ private static final boolean DEBUG = true;
public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
@@ -169,7 +169,7 @@
public AppInfo addPromiseApp(
Context context, PackageInstallInfo installInfo, boolean loadIcon) {
// only if not yet installed
- if (new PackageManagerHelper(context)
+ if (PackageManagerHelper.INSTANCE.get(context)
.isAppInstalled(installInfo.packageName, installInfo.user)) {
return null;
}
@@ -220,6 +220,11 @@
updatedAppInfos.add(appInfo);
} else if (installInfo.state == PackageInstallInfo.STATUS_FAILED
&& !appInfo.isAppStartable()) {
+ if (DEBUG) {
+ Log.w(TAG, "updatePromiseInstallInfo: removing app due to install"
+ + " failure and appInfo not startable."
+ + " package=" + appInfo.getTargetPackage());
+ }
removeApp(i);
}
}
@@ -299,6 +304,9 @@
*/
public List<LauncherActivityInfo> updatePackage(
Context context, String packageName, UserHandle user) {
+ final ApiWrapper apiWrapper = ApiWrapper.INSTANCE.get(context);
+ final UserCache userCache = UserCache.getInstance(context);
+ final PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context);
final List<LauncherActivityInfo> matches = context.getSystemService(LauncherApps.class)
.getActivityList(packageName, user);
if (matches.size() > 0) {
@@ -309,7 +317,10 @@
if (user.equals(applicationInfo.user)
&& packageName.equals(applicationInfo.componentName.getPackageName())) {
if (!findActivity(matches, applicationInfo.componentName)) {
- Log.w(TAG, "Changing shortcut target due to app component name change.");
+ if (DEBUG) {
+ Log.w(TAG, "Changing shortcut target due to app component name change."
+ + " package=" + packageName);
+ }
removeApp(i);
}
}
@@ -326,24 +337,18 @@
mIconCache.getTitleAndIcon(applicationInfo, info, false /* useLowResIcon */);
applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
- applicationInfo.setProgressLevel(
- PackageManagerHelper.getLoadingProgress(info),
- PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
applicationInfo.intent = launchIntent;
- if (Utilities.enableSupportForArchiving()) {
- // In case an app is archived, the respective item flag corresponding to
- // archiving should also be applied during package updates
- if (info.getActivityInfo().isArchived) {
- applicationInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
- } else {
- applicationInfo.runtimeStatusFlags &= (~FLAG_ARCHIVED);
- }
- }
+ AppInfo.updateRuntimeFlagsForActivityTarget(applicationInfo, info,
+ userCache.getUserInfo(user), apiWrapper, pmHelper);
mDataChanged = true;
}
}
} else {
// Remove all data for this package.
+ if (DEBUG) {
+ Log.w(TAG, "updatePromiseInstallInfo: no Activities matched updated package,"
+ + " removing all apps from package=" + packageName);
+ }
for (int i = data.size() - 1; i >= 0; i--) {
final AppInfo applicationInfo = data.get(i);
if (user.equals(applicationInfo.user)
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index fa2a1b0..e6ade61 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -16,8 +16,9 @@
package com.android.launcher3.model;
+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.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -29,6 +30,8 @@
import android.util.Pair;
import android.view.View;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.CallbackTask;
@@ -41,6 +44,7 @@
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.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInflater;
@@ -48,10 +52,12 @@
import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -63,7 +69,7 @@
/**
* Binds the results of {@link com.android.launcher3.model.LoaderTask} to the Callbacks objects.
*/
-public abstract class BaseLauncherBinder {
+public class BaseLauncherBinder {
protected static final String TAG = "LauncherBinder";
private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
@@ -79,8 +85,8 @@
private int mMyBindingId;
public BaseLauncherBinder(LauncherAppState app, BgDataModel dataModel,
- AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor) {
- mUiExecutor = uiExecutor;
+ AllAppsList allAppsList, Callbacks[] callbacksList) {
+ mUiExecutor = MAIN_EXECUTOR;
mApp = app;
mBgDataModel = dataModel;
mBgAllAppsList = allAppsList;
@@ -156,7 +162,16 @@
/**
* BindDeepShortcuts is abstract because it is a no-op for the go launcher.
*/
- public abstract void bindDeepShortcuts();
+ public void bindDeepShortcuts() {
+ if (!WIDGETS_ENABLED) {
+ return;
+ }
+ final HashMap<ComponentKey, Integer> shortcutMapCopy;
+ synchronized (mBgDataModel) {
+ shortcutMapCopy = new HashMap<>(mBgDataModel.deepShortcutMap);
+ }
+ executeCallbacksTask(c -> c.bindDeepShortcutMap(shortcutMapCopy), mUiExecutor);
+ }
/**
* Binds the all apps results from LoaderTask to the callbacks UX.
@@ -176,12 +191,24 @@
/**
* bindWidgets is abstract because it is a no-op for the go launcher.
*/
- public abstract void bindWidgets();
+ public void bindWidgets() {
+ if (!WIDGETS_ENABLED) {
+ return;
+ }
+ final List<WidgetsListBaseEntry> widgets =
+ mBgDataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
+ executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
+ }
/**
* bindWidgets is abstract because it is a no-op for the go launcher.
*/
- public abstract void bindSmartspaceWidget();
+ public void bindSmartspaceWidget() {
+ if (!WIDGETS_ENABLED) {
+ return;
+ }
+ executeCallbacksTask(c -> c.bindSmartspaceWidget(), mUiExecutor);
+ }
/**
* Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
@@ -300,7 +327,7 @@
executeCallbacksTask(c -> {
c.clearPendingBinds();
c.startBinding();
- if (ENABLE_SMARTSPACE_REMOVAL.get()) {
+ if (enableSmartspaceRemovalToggle()) {
c.setIsFirstPagePinnedItemEnabled(
mBgDataModel.isFirstPagePinnedItemEnabled);
}
@@ -309,9 +336,16 @@
// Bind workspace screens
executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
+ ItemInflater inflater = mCallbacks.getItemInflater();
+
// Load items on the current page.
- bindItemsInChunks(currentWorkspaceItems, ITEMS_CHUNK, mUiExecutor);
- bindItemsInChunks(currentAppWidgets, 1, mUiExecutor);
+ if (enableWorkspaceInflation() && inflater != null) {
+ inflateAsyncAndBind(currentWorkspaceItems, inflater, mUiExecutor);
+ inflateAsyncAndBind(currentAppWidgets, inflater, mUiExecutor);
+ } else {
+ bindItemsInChunks(currentWorkspaceItems, ITEMS_CHUNK, mUiExecutor);
+ bindItemsInChunks(currentAppWidgets, 1, mUiExecutor);
+ }
if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mExtraItems.forEach(item ->
executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
@@ -322,18 +356,20 @@
RunnableList onCompleteSignal = new RunnableList();
- if (enableWorkspaceInflation()) {
+ if (enableWorkspaceInflation() && inflater != null) {
MODEL_EXECUTOR.execute(() -> {
- setupPendingBind(otherWorkspaceItems, otherAppWidgets, currentScreenIds,
- pendingExecutor);
+ inflateAsyncAndBind(otherWorkspaceItems, inflater, pendingExecutor);
+ inflateAsyncAndBind(otherAppWidgets, inflater, pendingExecutor);
+ setupPendingBind(currentScreenIds, pendingExecutor);
// Wait for the async inflation to complete and then notify the completion
// signal on UI thread.
MAIN_EXECUTOR.execute(onCompleteSignal::executeAllAndDestroy);
});
} else {
- setupPendingBind(
- otherWorkspaceItems, otherAppWidgets, currentScreenIds, pendingExecutor);
+ bindItemsInChunks(otherWorkspaceItems, ITEMS_CHUNK, pendingExecutor);
+ bindItemsInChunks(otherAppWidgets, 1, pendingExecutor);
+ setupPendingBind(currentScreenIds, pendingExecutor);
onCompleteSignal.executeAllAndDestroy();
}
@@ -348,13 +384,8 @@
}
private void setupPendingBind(
- List<ItemInfo> otherWorkspaceItems,
- List<ItemInfo> otherAppWidgets,
IntSet currentScreenIds,
Executor pendingExecutor) {
- bindItemsInChunks(otherWorkspaceItems, ITEMS_CHUNK, pendingExecutor);
- bindItemsInChunks(otherAppWidgets, 1, pendingExecutor);
-
StringCache cacheClone = mBgDataModel.stringCache.clone();
executeCallbacksTask(c -> c.bindStringCache(cacheClone), pendingExecutor);
@@ -371,18 +402,11 @@
* Tries to inflate the items asynchronously and bind. Returns true on success or false if
* async-binding is not supported in this case.
*/
- private boolean inflateAsyncAndBind(List<ItemInfo> items, Executor executor) {
- if (!enableWorkspaceInflation()) {
- return false;
- }
- ItemInflater inflater = mCallbacks.getItemInflater();
- if (inflater == null) {
- return false;
- }
-
+ private void inflateAsyncAndBind(
+ List<ItemInfo> items, @NonNull ItemInflater inflater, Executor executor) {
if (mMyBindingId != mBgDataModel.lastBindId) {
Log.d(TAG, "Too many consecutive reloads, skipping obsolete view inflation");
- return true;
+ return;
}
ModelWriter writer = mApp.getModel()
@@ -390,15 +414,10 @@
List<Pair<ItemInfo, View>> bindItems = items.stream().map(i ->
Pair.create(i, inflater.inflateItem(i, writer, null))).toList();
executeCallbacksTask(c -> c.bindInflatedItems(bindItems), executor);
- return true;
}
- private void bindItemsInChunks(List<ItemInfo> workspaceItems, int chunkCount,
- Executor executor) {
- if (inflateAsyncAndBind(workspaceItems, executor)) {
- return;
- }
-
+ private void bindItemsInChunks(
+ List<ItemInfo> workspaceItems, int chunkCount, Executor executor) {
// Bind the workspace items
int count = workspaceItems.size();
for (int i = 0; i < count; i += chunkCount) {
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
deleted file mode 100644
index 529c30a..0000000
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ /dev/null
@@ -1,160 +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.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.celllayout.CellPosMapper;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-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.PackageUserKey;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * Extension of {@link ModelUpdateTask} with some utility methods
- */
-public abstract class BaseModelUpdateTask implements ModelUpdateTask {
-
- private static final boolean DEBUG_TASKS = false;
- private static final String TAG = "BaseModelUpdateTask";
-
- // Nullabilities are explicitly omitted here because these are late-init fields,
- // They will be non-null after init(), which is always the case in enqueueModelUpdateTask().
- private LauncherAppState mApp;
- private LauncherModel mModel;
- private BgDataModel mDataModel;
- private AllAppsList mAllAppsList;
- private Executor mUiExecutor;
-
- public void init(@NonNull final LauncherAppState app, @NonNull final LauncherModel model,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList allAppsList,
- @NonNull final Executor uiExecutor) {
- mApp = app;
- mModel = model;
- mDataModel = dataModel;
- mAllAppsList = allAppsList;
- mUiExecutor = uiExecutor;
- }
-
- @Override
- public final void run() {
- boolean isModelLoaded = Objects.requireNonNull(mModel).isModelLoaded();
- if (!isModelLoaded) {
- if (DEBUG_TASKS) {
- Log.d(TAG, "Ignoring model task since loader is pending=" + this);
- }
- // Loader has not yet run.
- return;
- }
- execute(mApp, mDataModel, mAllAppsList);
- }
-
- /**
- * Execute the actual task. Called on the worker thread.
- */
- public abstract void execute(@NonNull LauncherAppState app,
- @NonNull BgDataModel dataModel, @NonNull AllAppsList apps);
-
- /**
- * Schedules a {@param task} to be executed on the current callbacks.
- */
- public final void scheduleCallbackTask(@NonNull final CallbackTask task) {
- for (final Callbacks cb : mModel.getCallbacks()) {
- mUiExecutor.execute(() -> task.execute(cb));
- }
- }
-
- public ModelWriter getModelWriter() {
- // Updates from model task, do not deal with icon position in hotseat. Also no need to
- // verify changes as the ModelTasks always push the changes to callbacks
- return mModel.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null);
- }
-
- public void bindUpdatedWorkspaceItems(@NonNull final List<WorkspaceItemInfo> allUpdates) {
- // Bind workspace items
- List<WorkspaceItemInfo> workspaceUpdates = allUpdates.stream()
- .filter(info -> info.id != ItemInfo.NO_ID)
- .collect(Collectors.toList());
- if (!workspaceUpdates.isEmpty()) {
- scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(workspaceUpdates));
- }
-
- // Bind extra items if any
- allUpdates.stream()
- .mapToInt(info -> info.container)
- .distinct()
- .mapToObj(mDataModel.extraItems::get)
- .filter(Objects::nonNull)
- .forEach(this::bindExtraContainerItems);
- }
-
- public void bindExtraContainerItems(@NonNull final FixedContainerItems item) {
- scheduleCallbackTask(c -> c.bindExtraContainerItems(item));
- }
-
- public void bindDeepShortcuts(@NonNull final BgDataModel dataModel) {
- final HashMap<ComponentKey, Integer> shortcutMapCopy =
- new HashMap<>(dataModel.deepShortcutMap);
- scheduleCallbackTask(callbacks -> callbacks.bindDeepShortcutMap(shortcutMapCopy));
- }
-
- public void bindUpdatedWidgets(@NonNull final BgDataModel dataModel) {
- final ArrayList<WidgetsListBaseEntry> widgets =
- dataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
- scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
- }
-
- public void deleteAndBindComponentsRemoved(final Predicate<ItemInfo> matcher,
- @Nullable final String reason) {
- getModelWriter().deleteItemsFromDatabase(matcher, reason);
-
- // Call the components-removed callback
- scheduleCallbackTask(c -> c.bindWorkspaceComponentsRemoved(matcher));
- }
-
- public void bindApplicationsIfNeeded() {
- if (mAllAppsList.getAndResetChangeFlag()) {
- AppInfo[] apps = mAllAppsList.copyData();
- int flags = mAllAppsList.getFlags();
- Map<PackageUserKey, Integer> packageUserKeytoUidMap = Arrays.stream(apps).collect(
- Collectors.toMap(
- appInfo -> new PackageUserKey(appInfo.componentName.getPackageName(),
- appInfo.user), appInfo -> appInfo.uid, (a, b) -> a));
- scheduleCallbackTask(c -> c.bindAllApplications(apps, flags, packageUserKeytoUidMap));
- }
- }
-}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 8579d1d..9a9fa5b 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -18,9 +18,9 @@
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
-import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
-import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
+import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
import static java.util.stream.Collectors.groupingBy;
@@ -44,6 +44,8 @@
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;
@@ -102,9 +104,9 @@
public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
/**
- * Map of id to FolderInfos of all the folders created by LauncherModel
+ * Map of id to CollectionInfos of all the folders or app pairs created by LauncherModel
*/
- public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
+ public final IntSparseArrayMap<CollectionInfo> collections = new IntSparseArrayMap<>();
/**
* Extra container based items
@@ -136,7 +138,7 @@
*/
public int lastLoadId = -1;
public boolean isFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN
- && !ENABLE_SMARTSPACE_REMOVAL.get();
+ && !enableSmartspaceRemovalToggle();
/**
* Clears all the data
@@ -144,7 +146,7 @@
public synchronized void clear() {
workspaceItems.clear();
appWidgets.clear();
- folders.clear();
+ collections.clear();
itemsIdMap.clear();
deepShortcutMap.clear();
extraItems.clear();
@@ -161,7 +163,7 @@
}
}
if ((FeatureFlags.QSB_ON_FIRST_SCREEN
- && !shouldShowFirstPageWidget())
+ && !SHOULD_SHOW_FIRST_PAGE_WIDGET)
|| screenSet.isEmpty()) {
screenSet.add(Workspace.FIRST_SCREEN_ID);
}
@@ -179,9 +181,9 @@
for (int i = 0; i < appWidgets.size(); i++) {
writer.println(prefix + '\t' + appWidgets.get(i).toString());
}
- writer.println(prefix + " ---- folder items ");
- for (int i = 0; i < folders.size(); i++) {
- writer.println(prefix + '\t' + folders.valueAt(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 + " ---- extra items ");
for (int i = 0; i < extraItems.size(); i++) {
@@ -211,12 +213,12 @@
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
- folders.remove(item.id);
+ collections.remove(item.id);
if (FeatureFlags.IS_STUDIO_BUILD) {
for (ItemInfo info : itemsIdMap) {
if (info.container == item.id) {
- // We are deleting a folder which still contains items that
- // think they are contained by that folder.
+ // 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);
@@ -258,10 +260,15 @@
itemsIdMap.put(item.id, item);
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
- folders.put(item.id, (FolderInfo) item);
+ 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 ||
@@ -269,14 +276,14 @@
workspaceItems.add(item);
} else {
if (newItem) {
- if (!folders.containsKey(item.container)) {
+ 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((WorkspaceItemInfo) item, false);
+ findOrMakeFolder(item.container).add(item);
}
}
break;
@@ -305,7 +312,7 @@
* shortcuts and unpinning any extra shortcuts.
*/
public synchronized void updateShortcutPinnedState(Context context, UserHandle user) {
- if (GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
return;
}
@@ -371,15 +378,18 @@
* Return an existing FolderInfo object if we have encountered this ID previously,
* or make a new one.
*/
- public synchronized FolderInfo findOrMakeFolder(int id) {
+ public synchronized CollectionInfo findOrMakeFolder(int id) {
// See if a placeholder was created for us already
- FolderInfo folderInfo = folders.get(id);
- if (folderInfo == null) {
- // No placeholder -- create a new instance
- folderInfo = new FolderInfo();
- folders.put(id, folderInfo);
+ 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 folderInfo;
+ return collectionInfo;
}
/**
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 57fefaa..66b4fd9 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -20,7 +20,7 @@
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.IconCache;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -31,7 +31,7 @@
/**
* Handles changes due to cache updates.
*/
-public class CacheDataUpdatedTask extends BaseModelUpdateTask {
+public class CacheDataUpdatedTask implements ModelUpdateTask {
public static final int OP_CACHE_UPDATE = 1;
public static final int OP_SESSION_UPDATE = 2;
@@ -52,9 +52,9 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
- IconCache iconCache = app.getIconCache();
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
+ IconCache iconCache = taskController.getApp().getIconCache();
ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
synchronized (dataModel) {
@@ -69,8 +69,8 @@
});
apps.updateIconsAndLabels(mPackages, mUser);
}
- bindUpdatedWorkspaceItems(updatedShortcuts);
- bindApplicationsIfNeeded();
+ taskController.bindUpdatedWorkspaceItems(updatedShortcuts);
+ taskController.bindApplicationsIfNeeded();
}
public boolean isValidShortcut(@NonNull final WorkspaceItemInfo si) {
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index 1360510..8368256 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -16,7 +16,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
-import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
+import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import android.content.ContentValues;
@@ -259,7 +259,7 @@
}
case 30: {
if (FeatureFlags.QSB_ON_FIRST_SCREEN
- && !shouldShowFirstPageWidget()) {
+ && !SHOULD_SHOW_FIRST_PAGE_WIDGET) {
// Clean up first row in screen 0 as it might contain junk data.
Log.d(TAG, "Cleaning up first row");
db.delete(Favorites.TABLE_NAME,
@@ -333,7 +333,7 @@
for (int widgetId : allWidgets) {
if (!validWidgets.contains(widgetId)) {
try {
- FileLog.d(TAG, "Deleting invalid widget " + widgetId);
+ FileLog.d(TAG, "Deleting widget not found in db: appWidgetId=" + widgetId);
holder.deleteAppWidgetId(widgetId);
isAnyWidgetRemoved = true;
} catch (RuntimeException e) {
@@ -342,15 +342,17 @@
}
}
if (isAnyWidgetRemoved) {
- final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf)
+ final String allLauncherHostWidgetIds = Arrays.stream(allWidgets)
+ .mapToObj(String::valueOf)
.collect(Collectors.joining(",", "[", "]"));
- final String validWidgetsIds = Arrays.stream(
+ final String allValidLauncherDbWidgetIds = Arrays.stream(
validWidgets.getArray().toArray()).mapToObj(String::valueOf)
.collect(Collectors.joining(",", "[", "]"));
FileLog.d(TAG,
- "One or more widgets was removed. db_path=" + db.getPath()
- + " allWidgetsIds=" + allWidgetsIds
- + ", validWidgetsIds=" + validWidgetsIds);
+ "One or more widgets was removed: "
+ + " allLauncherHostWidgetIds=" + allLauncherHostWidgetIds
+ + ", allValidLauncherDbWidgetIds=" + allValidLauncherDbWidgetIds
+ );
}
} finally {
holder.destroy();
@@ -503,7 +505,7 @@
public int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
// TODO: Use multiple loaders with fall-back and transaction.
- int count = loader.loadLayout(db, new IntArray());
+ int count = loader.loadLayout(db);
// Ensure that the max ids are initialized
mMaxItemId = initializeMaxItemId(db);
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index f24d1d2..729b381 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -53,6 +53,13 @@
private final @DeviceType int mDeviceType;
private final String mDbFile;
+ public DeviceGridState(int columns, int row, int numHotseat, int deviceType, String dbFile) {
+ mGridSizeString = String.format(Locale.ENGLISH, "%d,%d", columns, row);
+ mNumHotseat = numHotseat;
+ mDeviceType = deviceType;
+ mDbFile = dbFile;
+ }
+
public DeviceGridState(InvariantDeviceProfile idp) {
mGridSizeString = String.format(Locale.ENGLISH, "%d,%d", idp.numColumns, idp.numRows);
mNumHotseat = idp.numDatabaseHotseatIcons;
@@ -149,11 +156,11 @@
}
public Integer getColumns() {
- return Integer.parseInt(String.valueOf(mGridSizeString.charAt(0)));
+ return Integer.parseInt(String.valueOf(mGridSizeString.split(",")[0]));
}
public Integer getRows() {
- return Integer.parseInt(String.valueOf(mGridSizeString.charAt(2)));
+ return Integer.parseInt(String.valueOf(mGridSizeString.split(",")[1]));
}
@Override
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 9e91b9d..f443f81 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -36,6 +36,7 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherSettings;
+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;
@@ -67,7 +68,8 @@
private static final String ACTION_FIRST_SCREEN_ACTIVE_INSTALLS
= "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS";
- private static final String FOLDER_ITEM_EXTRA = "folderItem";
+ // String retained as "folderItem" for back-compatibility reasons.
+ private static final String COLLECTION_ITEM_EXTRA = "folderItem";
private static final String WORKSPACE_ITEM_EXTRA = "workspaceItem";
private static final String HOTSEAT_ITEM_EXTRA = "hotseatItem";
private static final String WIDGET_ITEM_EXTRA = "widgetItem";
@@ -94,7 +96,7 @@
.collect(groupingBy(SessionInfo::getInstallerPackageName,
mapping(SessionInfo::getAppPackageName, Collectors.toSet())))
.forEach((installer, packages) ->
- sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
+ sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
}
/**
@@ -105,20 +107,19 @@
@WorkerThread
private void sendBroadcastToInstaller(Context context, String installerPackageName,
Set<String> packages, List<ItemInfo> firstScreenItems) {
- Set<String> folderItems = new HashSet<>();
+ Set<String> collectionItems = new HashSet<>();
Set<String> workspaceItems = new HashSet<>();
Set<String> hotseatItems = new HashSet<>();
Set<String> widgetItems = new HashSet<>();
for (ItemInfo info : firstScreenItems) {
- if (info instanceof FolderInfo) {
- FolderInfo folderInfo = (FolderInfo) info;
- String folderItemInfoPackage;
- for (ItemInfo folderItemInfo : cloneOnMainThread(folderInfo.contents)) {
- folderItemInfoPackage = getPackageName(folderItemInfo);
- if (folderItemInfoPackage != null
- && packages.contains(folderItemInfoPackage)) {
- folderItems.add(folderItemInfoPackage);
+ if (info instanceof CollectionInfo ci) {
+ String collectionItemInfoPackage;
+ for (ItemInfo collectionItemInfo : cloneOnMainThread(ci.getAppContents())) {
+ collectionItemInfoPackage = getPackageName(collectionItemInfo);
+ if (collectionItemInfoPackage != null
+ && packages.contains(collectionItemInfoPackage)) {
+ collectionItems.add(collectionItemInfoPackage);
}
}
}
@@ -137,13 +138,13 @@
}
if (DEBUG) {
- printList(installerPackageName, "Folder item", folderItems);
+ printList(installerPackageName, "Collection item", collectionItems);
printList(installerPackageName, "Workspace item", workspaceItems);
printList(installerPackageName, "Hotseat item", hotseatItems);
printList(installerPackageName, "Widget item", widgetItems);
}
- if (folderItems.isEmpty()
+ if (collectionItems.isEmpty()
&& workspaceItems.isEmpty()
&& hotseatItems.isEmpty()
&& widgetItems.isEmpty()) {
@@ -152,7 +153,7 @@
}
context.sendBroadcast(new Intent(ACTION_FIRST_SCREEN_ACTIVE_INSTALLS)
.setPackage(installerPackageName)
- .putStringArrayListExtra(FOLDER_ITEM_EXTRA, new ArrayList<>(folderItems))
+ .putStringArrayListExtra(COLLECTION_ITEM_EXTRA, new ArrayList<>(collectionItems))
.putStringArrayListExtra(WORKSPACE_ITEM_EXTRA, new ArrayList<>(workspaceItems))
.putStringArrayListExtra(HOTSEAT_ITEM_EXTRA, new ArrayList<>(hotseatItems))
.putStringArrayListExtra(WIDGET_ITEM_EXTRA, new ArrayList<>(widgetItems))
@@ -180,7 +181,7 @@
}
/**
- * Clone the provided list on UI thread. This is used for {@link FolderInfo#contents} which
+ * Clone the provided list on UI thread. This is used for {@link FolderInfo#getContents()} which
* is always modified on UI thread.
*/
@AnyThread
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
new file mode 100644
index 0000000..aa62c32
--- /dev/null
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageInstaller.SessionInfo
+import android.os.Process
+import android.util.Log
+import androidx.annotation.AnyThread
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.model.data.CollectionInfo
+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
+
+/**
+ * Helper class to send broadcasts to package installers that have:
+ * - Pending Items on first screen
+ * - Installed/Archived Items on first screen
+ * - Installed/Archived Widgets on every screen
+ *
+ * The packages are broken down by: folder items, workspace items, hotseat items, and widgets.
+ * Package installers only receive data for items that they are installing or have installed.
+ */
+object FirstScreenBroadcastHelper {
+ @VisibleForTesting const val MAX_BROADCAST_SIZE = 70
+
+ private const val TAG = "FirstScreenBroadcastHelper"
+ private const val DEBUG = true
+ private const val ACTION_FIRST_SCREEN_ACTIVE_INSTALLS =
+ "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS"
+ // String retained as "folderItem" for back-compatibility reasons.
+ private const val PENDING_COLLECTION_ITEM_EXTRA = "folderItem"
+ private const val PENDING_WORKSPACE_ITEM_EXTRA = "workspaceItem"
+ private const val PENDING_HOTSEAT_ITEM_EXTRA = "hotseatItem"
+ private const val PENDING_WIDGET_ITEM_EXTRA = "widgetItem"
+ // Extras containing all installed items, including Archived Apps.
+ private const val INSTALLED_WORKSPACE_ITEMS_EXTRA = "workspaceInstalledItems"
+ private const val INSTALLED_HOTSEAT_ITEMS_EXTRA = "hotseatInstalledItems"
+ // This includes installed widgets on all screens, not just first.
+ private const val ALL_INSTALLED_WIDGETS_ITEM_EXTRA = "widgetInstalledItems"
+ private const val VERIFICATION_TOKEN_EXTRA = "verificationToken"
+
+ /**
+ * Return list of [FirstScreenBroadcastModel] for each installer and their
+ * installing/installed/archived items. If the FirstScreenBroadcastModel data is greater in size
+ * than [MAX_BROADCAST_SIZE], then we will truncate the data until it meets the size limit to
+ * avoid overloading the broadcast.
+ *
+ * @param packageManagerHelper helper for querying PackageManager
+ * @param firstScreenItems every ItemInfo on first screen
+ * @param userKeyToSessionMap map of pending SessionInfo's for installing items
+ * @param allWidgets list of all Widgets added to every screen
+ */
+ @WorkerThread
+ @JvmStatic
+ fun createModelsForFirstScreenBroadcast(
+ packageManagerHelper: PackageManagerHelper,
+ firstScreenItems: List<ItemInfo>,
+ userKeyToSessionMap: Map<PackageUserKey, SessionInfo>,
+ allWidgets: List<LauncherAppWidgetInfo>
+ ): List<FirstScreenBroadcastModel> {
+
+ // installers for installing items
+ val pendingItemInstallerMap: Map<String, MutableSet<String>> =
+ createPendingItemsMap(userKeyToSessionMap)
+ val installingPackages = pendingItemInstallerMap.values.flatten().toSet()
+
+ // installers for installed items on first screen
+ val installedItemInstallerMap: Map<String, MutableSet<ItemInfo>> =
+ createInstalledItemsMap(firstScreenItems, installingPackages, packageManagerHelper)
+
+ // installers for widgets on all screens
+ val allInstalledWidgetsMap: Map<String, MutableSet<LauncherAppWidgetInfo>> =
+ createAllInstalledWidgetsMap(allWidgets, installingPackages, packageManagerHelper)
+
+ val allInstallers: Set<String> =
+ pendingItemInstallerMap.keys +
+ installedItemInstallerMap.keys +
+ allInstalledWidgetsMap.keys
+ val models = mutableListOf<FirstScreenBroadcastModel>()
+ // create broadcast for each installer, with extras for each item category
+ allInstallers.forEach { installer ->
+ val installingItems = pendingItemInstallerMap[installer]
+ val broadcastModel =
+ FirstScreenBroadcastModel(installerPackage = installer).apply {
+ addPendingItems(installingItems, firstScreenItems)
+ addInstalledItems(installer, installedItemInstallerMap)
+ addAllScreenWidgets(installer, allInstalledWidgetsMap)
+ }
+ broadcastModel.truncateModelForBroadcast()
+ models.add(broadcastModel)
+ }
+ return models
+ }
+
+ /** From the model data, create Intents to send broadcasts and fire them. */
+ @WorkerThread
+ @JvmStatic
+ fun sendBroadcastsForModels(context: Context, models: List<FirstScreenBroadcastModel>) {
+ for (model in models) {
+ model.printDebugInfo()
+ val intent =
+ Intent(ACTION_FIRST_SCREEN_ACTIVE_INSTALLS)
+ .setPackage(model.installerPackage)
+ .putExtra(
+ VERIFICATION_TOKEN_EXTRA,
+ PendingIntent.getActivity(
+ context,
+ 0 /* requestCode */,
+ Intent(),
+ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+ )
+ )
+ .putStringArrayListExtra(
+ PENDING_COLLECTION_ITEM_EXTRA,
+ ArrayList(model.pendingCollectionItems)
+ )
+ .putStringArrayListExtra(
+ PENDING_WORKSPACE_ITEM_EXTRA,
+ ArrayList(model.pendingWorkspaceItems)
+ )
+ .putStringArrayListExtra(
+ PENDING_HOTSEAT_ITEM_EXTRA,
+ ArrayList(model.pendingHotseatItems)
+ )
+ .putStringArrayListExtra(
+ PENDING_WIDGET_ITEM_EXTRA,
+ ArrayList(model.pendingWidgetItems)
+ )
+ .putStringArrayListExtra(
+ INSTALLED_WORKSPACE_ITEMS_EXTRA,
+ ArrayList(model.installedWorkspaceItems)
+ )
+ .putStringArrayListExtra(
+ INSTALLED_HOTSEAT_ITEMS_EXTRA,
+ ArrayList(model.installedHotseatItems)
+ )
+ .putStringArrayListExtra(
+ ALL_INSTALLED_WIDGETS_ITEM_EXTRA,
+ ArrayList(
+ model.firstScreenInstalledWidgets +
+ model.secondaryScreenInstalledWidgets
+ )
+ )
+ context.sendBroadcast(intent)
+ }
+ }
+
+ /** Maps Installer packages to Set of app packages from install sessions */
+ private fun createPendingItemsMap(
+ userKeyToSessionMap: Map<PackageUserKey, SessionInfo>
+ ): Map<String, MutableSet<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 result
+ }
+
+ /**
+ * Add first screen Pending Items from Map to [FirstScreenBroadcastModel] for given installer
+ */
+ private fun FirstScreenBroadcastModel.addPendingItems(
+ installingItems: Set<String>?,
+ firstScreenItems: List<ItemInfo>
+ ) {
+ if (installingItems == null) return
+ for (info in firstScreenItems) {
+ addCollectionItems(info, installingItems)
+ val packageName = getPackageName(info) ?: continue
+ if (!installingItems.contains(packageName)) continue
+ when {
+ info is LauncherAppWidgetInfo -> pendingWidgetItems.add(packageName)
+ info.container == CONTAINER_HOTSEAT -> pendingHotseatItems.add(packageName)
+ info.container == CONTAINER_DESKTOP -> pendingWorkspaceItems.add(packageName)
+ }
+ }
+ }
+
+ /**
+ * Add first screen installed Items from Map to [FirstScreenBroadcastModel] for given installer
+ */
+ private fun FirstScreenBroadcastModel.addInstalledItems(
+ installer: String,
+ installedItemInstallerMap: Map<String, Set<ItemInfo>>,
+ ) {
+ installedItemInstallerMap[installer]?.forEach { info ->
+ val packageName: String = getPackageName(info) ?: return@forEach
+ when (info.container) {
+ CONTAINER_HOTSEAT -> installedHotseatItems.add(packageName)
+ CONTAINER_DESKTOP -> installedWorkspaceItems.add(packageName)
+ }
+ }
+ }
+
+ /** Add Widgets on every screen from Map to [FirstScreenBroadcastModel] for given installer */
+ private fun FirstScreenBroadcastModel.addAllScreenWidgets(
+ installer: String,
+ allInstalledWidgetsMap: Map<String, Set<LauncherAppWidgetInfo>>
+ ) {
+ allInstalledWidgetsMap[installer]?.forEach { widget ->
+ val packageName: String = getPackageName(widget) ?: return@forEach
+ if (widget.screenId == 0) {
+ firstScreenInstalledWidgets.add(packageName)
+ } else {
+ secondaryScreenInstalledWidgets.add(packageName)
+ }
+ }
+ }
+
+ private fun FirstScreenBroadcastModel.addCollectionItems(
+ info: ItemInfo,
+ installingPackages: Set<String>
+ ) {
+ if (info !is CollectionInfo) return
+ pendingCollectionItems.addAll(
+ cloneOnMainThread(info.getAppContents())
+ .mapNotNull { getPackageName(it) }
+ .filter { installingPackages.contains(it) }
+ )
+ }
+
+ /**
+ * Creates a copy of [FirstScreenBroadcastModel] with items truncated to meet
+ * [MAX_BROADCAST_SIZE] in a prioritized order.
+ */
+ @VisibleForTesting
+ fun FirstScreenBroadcastModel.truncateModelForBroadcast() {
+ val totalItemCount = getTotalItemCount()
+ if (totalItemCount <= MAX_BROADCAST_SIZE) return
+ var extraItemCount = totalItemCount - MAX_BROADCAST_SIZE
+
+ while (extraItemCount > 0) {
+ // In this order, remove items until we meet the max size limit.
+ when {
+ pendingCollectionItems.isNotEmpty() ->
+ pendingCollectionItems.apply { remove(last()) }
+ pendingHotseatItems.isNotEmpty() -> pendingHotseatItems.apply { remove(last()) }
+ installedHotseatItems.isNotEmpty() -> installedHotseatItems.apply { remove(last()) }
+ secondaryScreenInstalledWidgets.isNotEmpty() ->
+ secondaryScreenInstalledWidgets.apply { remove(last()) }
+ pendingWidgetItems.isNotEmpty() -> pendingWidgetItems.apply { remove(last()) }
+ firstScreenInstalledWidgets.isNotEmpty() ->
+ firstScreenInstalledWidgets.apply { remove(last()) }
+ pendingWorkspaceItems.isNotEmpty() -> pendingWorkspaceItems.apply { remove(last()) }
+ installedWorkspaceItems.isNotEmpty() ->
+ installedWorkspaceItems.apply { remove(last()) }
+ }
+ extraItemCount--
+ }
+ }
+
+ /** Returns count of all Items held by [FirstScreenBroadcastModel]. */
+ @VisibleForTesting
+ fun FirstScreenBroadcastModel.getTotalItemCount() =
+ pendingCollectionItems.size +
+ pendingWorkspaceItems.size +
+ pendingHotseatItems.size +
+ pendingWidgetItems.size +
+ installedWorkspaceItems.size +
+ installedHotseatItems.size +
+ firstScreenInstalledWidgets.size +
+ secondaryScreenInstalledWidgets.size
+
+ private fun FirstScreenBroadcastModel.printDebugInfo() {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Sending First Screen Broadcast for installer=$installerPackage" +
+ ", total packages=${getTotalItemCount()}"
+ )
+ pendingCollectionItems.forEach {
+ Log.d(TAG, "$installerPackage:Pending Collection item:$it")
+ }
+ pendingWorkspaceItems.forEach {
+ Log.d(TAG, "$installerPackage:Pending Workspace item:$it")
+ }
+ pendingHotseatItems.forEach { Log.d(TAG, "$installerPackage:Pending Hotseat item:$it") }
+ pendingWidgetItems.forEach { Log.d(TAG, "$installerPackage:Pending Widget item:$it") }
+ installedWorkspaceItems.forEach {
+ Log.d(TAG, "$installerPackage:Installed Workspace item:$it")
+ }
+ installedHotseatItems.forEach {
+ Log.d(TAG, "$installerPackage:Installed Hotseat item:$it")
+ }
+ firstScreenInstalledWidgets.forEach {
+ Log.d(TAG, "$installerPackage:Installed Widget item (first screen):$it")
+ }
+ secondaryScreenInstalledWidgets.forEach {
+ Log.d(TAG, "$installerPackage:Installed Widget item (secondary screens):$it")
+ }
+ }
+ }
+
+ 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
+ }
+
+ /**
+ * Clone the provided list on UI thread. This is used for [FolderInfo.getContents] which is
+ * always modified on UI thread.
+ */
+ @AnyThread
+ private fun cloneOnMainThread(list: ArrayList<WorkspaceItemInfo>): List<WorkspaceItemInfo> {
+ return try {
+ return Executors.MAIN_EXECUTOR.submit<ArrayList<WorkspaceItemInfo>> { ArrayList(list) }
+ .get()
+ } catch (e: Exception) {
+ emptyList()
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastModel.kt b/src/com/android/launcher3/model/FirstScreenBroadcastModel.kt
new file mode 100644
index 0000000..ba5c526
--- /dev/null
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+/** Data model for the information used for [FirstScreenBroadcastHelper] Broadcast Extras */
+data class FirstScreenBroadcastModel(
+ // Package name of the installer for all items
+ val installerPackage: String,
+ // Installing items in Folders
+ val pendingCollectionItems: MutableSet<String> = mutableSetOf(),
+ // Installing items on first screen
+ val pendingWorkspaceItems: MutableSet<String> = mutableSetOf(),
+ // Installing items on hotseat
+ val pendingHotseatItems: MutableSet<String> = mutableSetOf(),
+ // Installing widgets on first screen
+ val pendingWidgetItems: MutableSet<String> = mutableSetOf(),
+ // Installed/Archived Items on first screen
+ val installedWorkspaceItems: MutableSet<String> = mutableSetOf(),
+ // Installed/Archived items on hotseat
+ val installedHotseatItems: MutableSet<String> = mutableSetOf(),
+ // Installed/Archived Widgets on first screen
+ val firstScreenInstalledWidgets: MutableSet<String> = mutableSetOf(),
+ // Installed Archived Widgets on secondary screens
+ val secondaryScreenInstalledWidgets: MutableSet<String> = mutableSetOf()
+)
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index af66431..ad32fc2 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -16,10 +16,10 @@
package com.android.launcher3.model;
+import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
-import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
+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;
@@ -38,7 +38,9 @@
import android.util.Log;
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;
@@ -47,6 +49,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
+import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -94,6 +97,15 @@
return needsToMigrate;
}
+ @VisibleForTesting
+ public static List<DbEntry> readAllEntries(SQLiteDatabase db, String tableName,
+ Context context) {
+ DbReader dbReader = new DbReader(db, tableName, context, getValidPackages(context));
+ List<DbEntry> result = dbReader.loadAllWorkspaceEntries();
+ result.addAll(dbReader.loadHotseatEntries());
+ return result;
+ }
+
/**
* When migrating the grid, we copy the table
* {@link LauncherSettings.Favorites#TABLE_NAME} from {@code source} into
@@ -105,15 +117,23 @@
*/
public static boolean migrateGridIfNeeded(
@NonNull Context context,
- @NonNull InvariantDeviceProfile idp,
+ @NonNull DeviceGridState srcDeviceState,
+ @NonNull DeviceGridState destDeviceState,
@NonNull DatabaseHelper target,
@NonNull SQLiteDatabase source) {
-
- DeviceGridState srcDeviceState = new DeviceGridState(context);
- DeviceGridState destDeviceState = new DeviceGridState(idp);
if (!needsToMigrate(srcDeviceState, destDeviceState)) {
return true;
}
+
+ if (Flags.enableGridMigrationFix()
+ && srcDeviceState.getColumns().equals(destDeviceState.getColumns())
+ && srcDeviceState.getRows() < destDeviceState.getRows()) {
+ // Only use this strategy when comparing the previous grid to the new grid and the
+ // columns are the same and the destination has more rows
+ copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context);
+ destDeviceState.writeToPrefs(context);
+ return true;
+ }
copyTable(source, TABLE_NAME, target.getWritableDatabase(), TMP_TABLE, context);
HashSet<String> validPackages = getValidPackages(context);
@@ -204,19 +224,13 @@
screens.add(screenId);
}
- boolean preservePages = false;
- if (screens.isEmpty() && FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.get()) {
- preservePages = destDeviceState.compareTo(srcDeviceState) >= 0
- && destDeviceState.getColumns() - srcDeviceState.getColumns() <= 2;
- }
-
// Then we place the items on the screens
for (int screenId : screens) {
if (DEBUG) {
Log.d(TAG, "Migrating " + screenId);
}
solveGridPlacement(helper, srcReader,
- destReader, screenId, trgX, trgY, workspaceToBeAdded, false);
+ destReader, screenId, trgX, trgY, workspaceToBeAdded);
if (workspaceToBeAdded.isEmpty()) {
break;
}
@@ -226,8 +240,8 @@
// any of the screens, in this case we add them to new screens until all of them are placed.
int screenId = destReader.mLastScreenId + 1;
while (!workspaceToBeAdded.isEmpty()) {
- solveGridPlacement(helper, srcReader,
- destReader, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
+ solveGridPlacement(helper, srcReader, destReader, screenId, trgX, trgY,
+ workspaceToBeAdded);
screenId++;
}
@@ -329,14 +343,14 @@
private static void solveGridPlacement(@NonNull final DatabaseHelper helper,
@NonNull final DbReader srcReader, @NonNull final DbReader destReader,
final int screenId, final int trgX, final int trgY,
- @NonNull final List<DbEntry> sortedItemsToPlace, final boolean matchingScreenIdOnly) {
+ @NonNull final List<DbEntry> sortedItemsToPlace) {
final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
final Point trg = new Point(trgX, trgY);
final Point next = new Point(0, screenId == 0
&& (FeatureFlags.QSB_ON_FIRST_SCREEN
- && (!ENABLE_SMARTSPACE_REMOVAL.get() || LauncherPrefs.getPrefs(destReader.mContext)
+ && (!enableSmartspaceRemovalToggle() || LauncherPrefs.getPrefs(destReader.mContext)
.getBoolean(SMARTSPACE_ON_HOME_SCREEN, true))
- && !shouldShowFirstPageWidget())
+ && !SHOULD_SHOW_FIRST_PAGE_WIDGET)
? 1 /* smartspace */ : 0);
List<DbEntry> existedEntries = destReader.mWorkspaceEntriesByScreenId.get(screenId);
if (existedEntries != null) {
@@ -347,8 +361,6 @@
Iterator<DbEntry> iterator = sortedItemsToPlace.iterator();
while (iterator.hasNext()) {
final DbEntry entry = iterator.next();
- if (matchingScreenIdOnly && entry.screenId < screenId) continue;
- if (matchingScreenIdOnly && entry.screenId > screenId) break;
if (entry.minSpanX > trgX || entry.minSpanY > trgY) {
iterator.remove();
continue;
@@ -416,7 +428,8 @@
}
}
- protected static class DbReader {
+ @VisibleForTesting
+ public static class DbReader {
private final SQLiteDatabase mDb;
private final String mTableName;
@@ -427,7 +440,7 @@
private final Map<Integer, ArrayList<DbEntry>> mWorkspaceEntriesByScreenId =
new ArrayMap<>();
- DbReader(SQLiteDatabase db, String tableName, Context context,
+ public DbReader(SQLiteDatabase db, String tableName, Context context,
Set<String> validPackages) {
mDb = db;
mTableName = tableName;
@@ -656,12 +669,17 @@
}
}
- protected static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
+ public static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
private String mIntent;
private String mProvider;
private Map<String, Set<Integer>> mFolderItems = new HashMap<>();
+ /**
+ * Id of the specific widget.
+ */
+ public int appWidgetId = NO_ID;
+
/** Comparator according to the reading order */
@Override
public int compareTo(DbEntry another) {
@@ -695,6 +713,18 @@
values.put(LauncherSettings.Favorites.SPANY, spanY);
}
+ @Override
+ public void writeToValues(@NonNull ContentWriter writer) {
+ super.writeToValues(writer);
+ writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
+ }
+
+ @Override
+ public void readFromValues(@NonNull ContentValues values) {
+ super.readFromValues(values);
+ appWidgetId = values.getAsInteger(LauncherSettings.Favorites.APPWIDGET_ID);
+ }
+
/** This id is not used in the DB is only used while doing the migration and it identifies
* an entry on each workspace. For example two calculator icons would have the same
* migration id even thought they have different database ids.
@@ -705,7 +735,10 @@
case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
return getFolderMigrationId();
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- return mProvider;
+ // mProvider is the app the widget belongs to and appWidgetId it's the unique
+ // is of the widget, we need both because if you remove a widget and then add it
+ // again, then it can change and the WidgetProvider would not know the widget.
+ return mProvider + appWidgetId;
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
final String intentStr = cleanIntentString(mIntent);
try {
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index d350879..551c2d8 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -40,11 +40,11 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -54,6 +54,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PersistedItemArray;
import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import java.util.HashSet;
@@ -64,7 +65,7 @@
/**
* Class to maintain a queue of pending items to be added to the workspace.
*/
-public class ItemInstallQueue {
+public class ItemInstallQueue implements SafeCloseable {
private static final String LOG = "ItemInstallQueue";
@@ -98,6 +99,9 @@
mContext = context;
}
+ @Override
+ public void close() {}
+
@WorkerThread
private void ensureQueueLoaded() {
Preconditions.assertWorkerThread();
@@ -300,7 +304,7 @@
} else {
lai = laiList.get(0);
si.intent = makeLaunchIntent(lai);
- if (Utilities.enableSupportForArchiving()
+ if (Flags.enableSupportForArchiving()
&& lai.getActivityInfo().isArchived) {
si.runtimeStatusFlags |= FLAG_ARCHIVED;
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 2f678a8..84130c7 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -17,7 +17,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
-import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
+import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -52,10 +52,12 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.UserIconInfo;
import java.net.URISyntaxException;
@@ -72,6 +74,7 @@
private final LauncherAppState mApp;
private final Context mContext;
+ private final PackageManagerHelper mPmHelper;
private final IconCache mIconCache;
private final InvariantDeviceProfile mIDP;
private final @Nullable LauncherRestoreEventLogger mRestoreEventLogger;
@@ -113,6 +116,7 @@
public int restoreFlag;
public LoaderCursor(Cursor cursor, LauncherAppState app, UserManagerState userManagerState,
+ PackageManagerHelper pmHelper,
@Nullable LauncherRestoreEventLogger restoreEventLogger) {
super(cursor);
@@ -120,6 +124,7 @@
allUsers = userManagerState.allUsers;
mContext = app.getContext();
mIconCache = app.getIconCache();
+ mPmHelper = pmHelper;
mIDP = app.getInvariantDeviceProfile();
mRestoreEventLogger = restoreEventLogger;
@@ -366,7 +371,8 @@
}
if (mActivityInfo != null) {
- AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo, userIconInfo);
+ AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo, userIconInfo,
+ ApiWrapper.INSTANCE.get(mContext), mPmHelper);
}
// from the db
@@ -548,7 +554,8 @@
if (!mOccupied.containsKey(item.screenId)) {
GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
if (item.screenId == Workspace.FIRST_SCREEN_ID && (FeatureFlags.QSB_ON_FIRST_SCREEN
- && !shouldShowFirstPageWidget() && isFirstPagePinnedItemEnabled)) {
+ && !SHOULD_SHOW_FIRST_PAGE_WIDGET
+ && isFirstPagePinnedItemEnabled)) {
// Mark the first X columns (X is width of the search container) in the first row as
// occupied (if the feature is enabled) in order to account for the search
// container.
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 17cef90..312c6f4 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -18,12 +18,11 @@
import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
import static com.android.launcher3.Flags.enableLauncherBrMetricsFixed;
+import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
+import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
-import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
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;
@@ -48,6 +47,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LongSparseArray;
@@ -77,6 +77,8 @@
import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
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;
@@ -88,6 +90,7 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.IntArray;
@@ -99,7 +102,6 @@
import com.android.launcher3.widget.WidgetInflater;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -132,11 +134,12 @@
private FirstScreenBroadcast mFirstScreenBroadcast;
@NonNull
- private final LauncherBinder mLauncherBinder;
+ private final BaseLauncherBinder mLauncherBinder;
private final LauncherApps mLauncherApps;
private final UserManager mUserManager;
private final UserCache mUserCache;
+ private final PackageManagerHelper mPmHelper;
private final InstallSessionHelper mSessionHelper;
private final IconCache mIconCache;
@@ -153,13 +156,13 @@
private String mDbName;
public LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
- ModelDelegate modelDelegate, @NonNull LauncherBinder launcherBinder) {
+ ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder) {
this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, new UserManagerState());
}
@VisibleForTesting
LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
- ModelDelegate modelDelegate, @NonNull LauncherBinder launcherBinder,
+ ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder,
UserManagerState userManagerState) {
mApp = app;
mBgAllAppsList = bgAllAppsList;
@@ -169,6 +172,7 @@
mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
mUserManager = mApp.getContext().getSystemService(UserManager.class);
mUserCache = UserCache.INSTANCE.get(mApp.getContext());
+ mPmHelper = PackageManagerHelper.INSTANCE.get(mApp.getContext());
mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext());
mIconCache = mApp.getIconCache();
mUserManagerState = userManagerState;
@@ -192,17 +196,32 @@
}
private void sendFirstScreenActiveInstallsBroadcast() {
- ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
- ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
-
// Screen set is never empty
IntArray allScreens = mBgDataModel.collectWorkspaceScreens();
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 */);
- mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
+ final int launcherBroadcastInstalledApps = Settings.Secure.getInt(
+ mApp.getContext().getContentResolver(),
+ "launcher_broadcast_installed_apps",
+ /* def= */ 0);
+ if (launcherBroadcastInstalledApps == 1 && mIsRestoreFromBackup) {
+ List<FirstScreenBroadcastModel> broadcastModels =
+ FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+ mPmHelper,
+ firstScreenItems,
+ mInstallingPkgsCached,
+ mBgDataModel.appWidgets
+ );
+ logASplit("Sending first screen broadcast with additional archiving Extras");
+ FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels);
+ } else {
+ mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
+ }
}
public void run() {
@@ -234,6 +253,7 @@
if (Objects.equals(mApp.getInvariantDeviceProfile().dbFile, mDbName)) {
verifyNotStopped();
sanitizeFolders(mItemsDeleted);
+ sanitizeAppPairs();
sanitizeWidgetsShortcutsAndPackages();
logASplit("sanitizeData");
}
@@ -245,7 +265,7 @@
mModelDelegate.workspaceLoadComplete();
// Notify the installer packages of packages with active installs on the first screen.
sendFirstScreenActiveInstallsBroadcast();
- logASplit("sendFirstScreenActiveInstallsBroadcast");
+ logASplit("sendFirstScreenBroadcast");
// Take a break
waitForIdle();
@@ -318,13 +338,13 @@
verifyNotStopped();
LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
- if (SMARTSPACE_AS_A_WIDGET.get() && prefs.get(SHOULD_SHOW_SMARTSPACE)) {
+ if (enableSmartspaceAsAWidget() && prefs.get(SHOULD_SHOW_SMARTSPACE)) {
mLauncherBinder.bindSmartspaceWidget();
// Turn off pref.
prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(false));
logASplit("bindSmartspaceWidget");
verifyNotStopped();
- } else if (!SMARTSPACE_AS_A_WIDGET.get() && WIDGET_ON_FIRST_SCREEN
+ } else if (!enableSmartspaceAsAWidget() && WIDGET_ON_FIRST_SCREEN
&& !prefs.get(LauncherPrefs.SHOULD_SHOW_SMARTSPACE)) {
// Turn on pref.
prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(true));
@@ -395,7 +415,7 @@
logASplit("workspaceDelegateItems");
}
mBgDataModel.isFirstPagePinnedItemEnabled = FeatureFlags.QSB_ON_FIRST_SCREEN
- && (!ENABLE_SMARTSPACE_REMOVAL.get() || LauncherPrefs.getPrefs(
+ && (!enableSmartspaceRemovalToggle() || LauncherPrefs.getPrefs(
mApp.getContext()).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true));
}
@@ -405,7 +425,6 @@
@Nullable LoaderMemoryLogger memoryLogger,
@Nullable LauncherRestoreEventLogger restoreEventLogger) {
final Context context = mApp.getContext();
- final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
final boolean isSdCardReady = Utilities.isBootCompleted();
final WidgetInflater widgetInflater = new WidgetInflater(context);
@@ -420,7 +439,7 @@
final HashMap<PackageUserKey, SessionInfo> installingPkgs =
mSessionHelper.getActiveSessions();
- if (Utilities.enableSupportForArchiving()) {
+ if (Flags.enableSupportForArchiving()) {
mInstallingPkgsCached = installingPkgs;
}
installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);
@@ -432,7 +451,8 @@
mShortcutKeyToPinnedShortcuts = new HashMap<>();
final LoaderCursor c = new LoaderCursor(
dbController.query(TABLE_NAME, null, selection, null, null),
- mApp, mUserManagerState, mIsRestoreFromBackup ? restoreEventLogger : null);
+ mApp, mUserManagerState, mPmHelper,
+ mIsRestoreFromBackup ? restoreEventLogger : null);
final Bundle extras = c.getExtras();
mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME);
try {
@@ -442,10 +462,10 @@
List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>();
WorkspaceItemProcessor itemProcessor = new WorkspaceItemProcessor(c, memoryLogger,
- mUserManagerState, mLauncherApps, mPendingPackages,
+ mUserCache, mUserManagerState, mLauncherApps, mPendingPackages,
mShortcutKeyToPinnedShortcuts, mApp, mBgDataModel,
mWidgetProvidersMap, installingPkgs, isSdCardReady,
- widgetInflater, pmHelper, iconRequestInfos, unlockedUsers,
+ widgetInflater, mPmHelper, iconRequestInfos, unlockedUsers,
allDeepShortcuts);
while (!mStopped && c.moveToNext()) {
@@ -482,14 +502,18 @@
}
/**
- * After all items have been processed and added to the BgDataModel, this method requests
- * high-res icons for the items that are part of an app pair
+ * After all items have been processed and added to the BgDataModel, this method sorts and
+ * requests high-res icons for the items that are part of an app pair.
*/
private void processAppPairItems() {
- mBgDataModel.workspaceItems.stream()
- .filter((itemInfo -> itemInfo.itemType == ITEM_TYPE_APP_PAIR))
- .forEach(fi -> ((FolderInfo) fi).contents.forEach(item ->
- mIconCache.getTitleAndIcon(item, false /*useLowResIcon*/)));
+ for (CollectionInfo collection : mBgDataModel.collections) {
+ if (!(collection instanceof AppPairInfo appPair)) {
+ continue;
+ }
+
+ appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
+ appPair.fetchHiResIconsIfNeeded(mIconCache);
+ }
}
/**
@@ -545,24 +569,29 @@
// Sort the folder items, update ranks, and make sure all preview items are high res.
List<FolderGridOrganizer> verifiers = mApp.getInvariantDeviceProfile().supportedProfiles
.stream().map(FolderGridOrganizer::new).toList();
- for (FolderInfo folder : mBgDataModel.folders) {
- Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
+ for (CollectionInfo collection : mBgDataModel.collections) {
+ if (!(collection instanceof FolderInfo folder)) {
+ continue;
+ }
+
+ folder.getContents().sort(Folder.ITEM_POS_COMPARATOR);
verifiers.forEach(verifier -> verifier.setFolderInfo(folder));
- int size = folder.contents.size();
+ int size = folder.getContents().size();
// Update ranks here to ensure there are no gaps caused by removed folder items.
// Ranks are the source of truth for folder items, so cellX and cellY can be
// ignored for now. Database will be updated once user manually modifies folder.
for (int rank = 0; rank < size; ++rank) {
- WorkspaceItemInfo info = folder.contents.get(rank);
- // rank is used differently in app pairs, so don't reset
- if (folder.itemType != ITEM_TYPE_APP_PAIR) {
- info.rank = rank;
- }
+ ItemInfo info = folder.getContents().get(rank);
+ info.rank = rank;
- if (info.usingLowResIcon() && info.itemType == Favorites.ITEM_TYPE_APPLICATION
+ if (info instanceof WorkspaceItemInfo wii
+ && wii.usingLowResIcon()
+ && wii.itemType == Favorites.ITEM_TYPE_APPLICATION
&& verifiers.stream().anyMatch(it -> it.isItemInPreview(info.rank))) {
- mIconCache.getTitleAndIcon(info, false);
+ mIconCache.getTitleAndIcon(wii, false);
+ } else if (info instanceof AppPairInfo api) {
+ api.fetchHiResIconsIfNeeded(mIconCache);
}
}
}
@@ -611,14 +640,32 @@
IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
synchronized (mBgDataModel) {
for (int folderId : deletedFolderIds) {
- mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
- mBgDataModel.folders.remove(folderId);
+ mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(folderId));
+ mBgDataModel.collections.remove(folderId);
mBgDataModel.itemsIdMap.remove(folderId);
}
}
}
}
+ /** Cleans up app pairs if they don't have the right number of member apps (2). */
+ private void sanitizeAppPairs() {
+ IntArray deletedAppPairIds = mApp.getModel().getModelDbController().deleteBadAppPairs();
+ IntArray deletedAppIds = mApp.getModel().getModelDbController().deleteUnparentedApps();
+
+ IntArray deleted = new IntArray();
+ deleted.addAll(deletedAppPairIds);
+ deleted.addAll(deletedAppIds);
+
+ synchronized (mBgDataModel) {
+ for (int id : deleted) {
+ mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(id));
+ mBgDataModel.collections.remove(id);
+ mBgDataModel.itemsIdMap.remove(id);
+ }
+ }
+ }
+
private void sanitizeWidgetsShortcutsAndPackages() {
Context context = mApp.getContext();
@@ -666,8 +713,9 @@
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
- AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user), quietMode);
- if (Utilities.enableSupportForArchiving() && app.getApplicationInfo().isArchived) {
+ AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user),
+ ApiWrapper.INSTANCE.get(mApp.getContext()), mPmHelper, quietMode);
+ if (Flags.enableSupportForArchiving() && app.getApplicationInfo().isArchived) {
// For archived apps, include progress info in case there is a pending
// install session post restart of device.
String appPackageName = app.getApplicationInfo().packageName;
@@ -754,16 +802,16 @@
private void loadFolderNames() {
FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
- mBgAllAppsList.data, mBgDataModel.folders);
+ mBgAllAppsList.data, mBgDataModel.collections);
synchronized (mBgDataModel) {
- for (int i = 0; i < mBgDataModel.folders.size(); i++) {
+ for (int i = 0; i < mBgDataModel.collections.size(); i++) {
FolderNameInfos suggestionInfos = new FolderNameInfos();
- FolderInfo info = mBgDataModel.folders.valueAt(i);
- if (info.suggestedFolderNames == null) {
- provider.getSuggestedFolderName(mApp.getContext(), info.contents,
+ CollectionInfo info = mBgDataModel.collections.valueAt(i);
+ if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) {
+ provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
suggestionInfos);
- info.suggestedFolderNames = suggestionInfos;
+ fi.suggestedFolderNames = suggestionInfos;
}
}
}
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index ba2b64d..7e1d40d 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -15,11 +15,15 @@
*/
package com.android.launcher3.model;
+import static android.provider.BaseColumns._ID;
import static android.util.Base64.NO_PADDING;
import static android.util.Base64.NO_WRAP;
import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
@@ -308,8 +312,12 @@
mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
: createDatabaseHelper(true /* forMigration */);
try {
- return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, idp, mOpenHelper,
- oldHelper.getWritableDatabase());
+ // This is the current grid we have, given by the mContext
+ DeviceGridState srcDeviceState = new DeviceGridState(mContext);
+ // This is the state we want to migrate to that is given by the idp
+ DeviceGridState destDeviceState = new DeviceGridState(idp);
+ return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, srcDeviceState,
+ destDeviceState, mOpenHelper, oldHelper.getWritableDatabase());
} catch (Exception e) {
FileLog.e(TAG, "Failed to migrate grid", e);
return false;
@@ -387,6 +395,68 @@
}
}
+ /**
+ * Deletes any app pair that doesn't contain 2 member apps from the DB.
+ * @return Ids of deleted app pairs.
+ */
+ @WorkerThread
+ public IntArray deleteBadAppPairs() {
+ createDbIfNotExists();
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+ // Select all entries with ITEM_TYPE = ITEM_TYPE_APP_PAIR whose id does not appear
+ // exactly twice in the CONTAINER column.
+ String selection =
+ ITEM_TYPE + " = " + ITEM_TYPE_APP_PAIR
+ + " AND " + _ID + " NOT IN"
+ + " (SELECT " + CONTAINER + " FROM " + TABLE_NAME
+ + " GROUP BY " + CONTAINER + " HAVING COUNT(*) = 2)";
+
+ IntArray appPairIds = LauncherDbUtils.queryIntArray(false, db, TABLE_NAME,
+ _ID, selection, null, null);
+ if (!appPairIds.isEmpty()) {
+ db.delete(TABLE_NAME, Utilities.createDbSelectionQuery(
+ _ID, appPairIds), null);
+ }
+ t.commit();
+ return appPairIds;
+ } catch (SQLException ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ return new IntArray();
+ }
+ }
+
+ /**
+ * Deletes any app with a container id that doesn't exist.
+ * @return Ids of deleted apps.
+ */
+ @WorkerThread
+ public IntArray deleteUnparentedApps() {
+ createDbIfNotExists();
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+ // Select all entries whose container id does not appear in the database.
+ String selection =
+ CONTAINER + " >= 0"
+ + " AND " + CONTAINER + " NOT IN"
+ + " (SELECT " + _ID + " FROM " + TABLE_NAME + ")";
+
+ IntArray appIds = LauncherDbUtils.queryIntArray(false, db, TABLE_NAME,
+ _ID, selection, null, null);
+ if (!appIds.isEmpty()) {
+ db.delete(TABLE_NAME, Utilities.createDbSelectionQuery(
+ _ID, appIds), null);
+ }
+ t.commit();
+ return appIds;
+ } catch (SQLException ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ return new IntArray();
+ }
+ }
+
private static void addModifiedTime(ContentValues values) {
values.put(LauncherSettings.Favorites.MODIFIED, System.currentTimeMillis());
}
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 7e7bfb3..2264d35 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -26,6 +26,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.ResourceBasedOverride;
import java.io.FileDescriptor;
@@ -41,32 +42,35 @@
* Creates and initializes a new instance of the delegate
*/
public static ModelDelegate newInstance(
- Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel,
- boolean isPrimaryInstance) {
+ Context context, LauncherAppState app, PackageManagerHelper pmHelper,
+ AllAppsList appsList, BgDataModel dataModel, boolean isPrimaryInstance) {
ModelDelegate delegate = Overrides.getObject(
ModelDelegate.class, context, R.string.model_delegate_class);
- delegate.init(context, app, appsList, dataModel, isPrimaryInstance);
+ delegate.init(app, pmHelper, appsList, dataModel, isPrimaryInstance);
return delegate;
}
- protected Context mContext;
+ protected final Context mContext;
+ protected PackageManagerHelper mPmHelper;
protected LauncherAppState mApp;
protected AllAppsList mAppsList;
protected BgDataModel mDataModel;
protected boolean mIsPrimaryInstance;
- public ModelDelegate() { }
+ public ModelDelegate(Context context) {
+ mContext = context;
+ }
/**
* Initializes the object with the given params.
*/
- private void init(Context context, LauncherAppState app, AllAppsList appsList,
+ private void init(LauncherAppState app, PackageManagerHelper pmHelper, AllAppsList appsList,
BgDataModel dataModel, boolean isPrimaryInstance) {
this.mApp = app;
+ this.mPmHelper = pmHelper;
this.mAppsList = appsList;
this.mDataModel = dataModel;
this.mIsPrimaryInstance = isPrimaryInstance;
- this.mContext = context;
}
/** Called periodically to validate and update any data */
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
new file mode 100644
index 0000000..266ed0c
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherModel
+import com.android.launcher3.LauncherModel.CallbackTask
+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 java.util.Objects
+import java.util.concurrent.Executor
+import java.util.function.Predicate
+
+/** Class with utility methods and properties for running a LauncherModel Task */
+class ModelTaskController(
+ val app: LauncherAppState,
+ val dataModel: BgDataModel,
+ val allAppsList: AllAppsList,
+ private val model: LauncherModel,
+ private val uiExecutor: Executor
+) {
+
+ /** Schedules a {@param task} to be executed on the current callbacks. */
+ fun scheduleCallbackTask(task: CallbackTask) {
+ for (cb in model.callbacks) {
+ uiExecutor.execute { task.execute(cb) }
+ }
+ }
+
+ /**
+ * Updates from model task, do not deal with icon position in hotseat. Also no need to verify
+ * changes as the ModelTasks always push the changes to callbacks
+ */
+ fun getModelWriter() = model.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null)
+
+ fun bindUpdatedWorkspaceItems(allUpdates: List<WorkspaceItemInfo>) {
+ // Bind workspace items
+ val workspaceUpdates =
+ allUpdates.stream().filter { info -> info.id != ItemInfo.NO_ID }.toList()
+ if (workspaceUpdates.isNotEmpty()) {
+ scheduleCallbackTask { it.bindWorkspaceItemsChanged(workspaceUpdates) }
+ }
+
+ // Bind extra items if any
+ allUpdates
+ .stream()
+ .mapToInt { info: WorkspaceItemInfo -> info.container }
+ .distinct()
+ .mapToObj { dataModel.extraItems.get(it) }
+ .filter { Objects.nonNull(it) }
+ .forEach { bindExtraContainerItems(it) }
+ }
+
+ fun bindExtraContainerItems(item: FixedContainerItems) {
+ scheduleCallbackTask { it.bindExtraContainerItems(item) }
+ }
+
+ fun bindDeepShortcuts(dataModel: BgDataModel) {
+ val shortcutMapCopy = HashMap(dataModel.deepShortcutMap)
+ scheduleCallbackTask { it.bindDeepShortcutMap(shortcutMapCopy) }
+ }
+
+ fun bindUpdatedWidgets(dataModel: BgDataModel) {
+ val widgets = dataModel.widgetsModel.getWidgetsListForPicker(app.context)
+ scheduleCallbackTask { it.bindAllWidgets(widgets) }
+ }
+
+ fun deleteAndBindComponentsRemoved(matcher: Predicate<ItemInfo?>, reason: String?) {
+ getModelWriter().deleteItemsFromDatabase(matcher, reason)
+
+ // Call the components-removed callback
+ scheduleCallbackTask { it.bindWorkspaceComponentsRemoved(matcher) }
+ }
+
+ fun bindApplicationsIfNeeded() {
+ if (allAppsList.getAndResetChangeFlag()) {
+ val apps = allAppsList.copyData()
+ val flags = allAppsList.flags
+ val packageUserKeyToUidMap =
+ apps.associateBy(
+ keySelector = { PackageUserKey(it.componentName!!.packageName, it.user) },
+ valueTransform = { it.uid }
+ )
+ scheduleCallbackTask { it.bindAllApplications(apps, flags, packageUserKeyToUidMap) }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 55093a3..b477cb1 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -37,7 +37,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -275,7 +275,7 @@
public void deleteItemsFromDatabase(@NonNull final Predicate<ItemInfo> matcher,
@Nullable final String reason) {
deleteItemsFromDatabase(StreamSupport.stream(mBgDataModel.itemsIdMap.spliterator(), false)
- .filter(matcher).collect(Collectors.toList()), reason);
+ .filter(matcher).collect(Collectors.toList()), reason);
}
/**
@@ -302,15 +302,15 @@
/**
* Remove the specified folder and all its contents from the database.
*/
- public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
+ public void deleteCollectionAndContentsFromDatabase(final CollectionInfo info) {
ModelVerifier verifier = new ModelVerifier();
notifyDelete(Collections.singleton(info));
enqueueDeleteRunnable(newModelTask(() -> {
mModel.getModelDbController().delete(Favorites.TABLE_NAME,
Favorites.CONTAINER + "=" + info.id, null);
- mBgDataModel.removeItem(mContext, info.contents);
- info.contents.clear();
+ mBgDataModel.removeItem(mContext, info.getContents());
+ info.getContents().clear();
mModel.getModelDbController().delete(Favorites.TABLE_NAME,
Favorites._ID + "=" + info.id, null);
@@ -458,12 +458,12 @@
if (item.container != Favorites.CONTAINER_DESKTOP &&
item.container != Favorites.CONTAINER_HOTSEAT) {
- // Item is in a folder, make sure this folder exists
- if (!mBgDataModel.folders.containsKey(item.container)) {
+ // Item is in a collection, make sure this collection exists
+ if (!mBgDataModel.collections.containsKey(item.container)) {
// An items container is being set to a that of an item which is not in
// the list of Folders.
String msg = "item: " + item + " container being set to: " +
- item.container + ", not in the list of folders";
+ item.container + ", not in the list of collections";
Log.e(TAG, msg);
}
}
diff --git a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
index b9fba9d..f924a9f 100644
--- a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
@@ -19,7 +19,7 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -31,7 +31,7 @@
/**
* Handles updates due to incremental download progress updates.
*/
-public class PackageIncrementalDownloadUpdatedTask extends BaseModelUpdateTask {
+public class PackageIncrementalDownloadUpdatedTask implements ModelUpdateTask {
@NonNull
private final UserHandle mUser;
@@ -49,8 +49,8 @@
}
@Override
- public void execute(@NonNull LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList appsList) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList appsList) {
PackageInstallInfo downloadInfo = new PackageInstallInfo(
mPackageName,
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING,
@@ -62,11 +62,11 @@
if (!updatedAppInfos.isEmpty()) {
for (AppInfo appInfo : updatedAppInfos) {
appInfo.runtimeStatusFlags &= ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
- scheduleCallbackTask(
+ taskController.scheduleCallbackTask(
c -> c.bindIncrementalDownloadProgressUpdated(appInfo));
}
}
- bindApplicationsIfNeeded();
+ taskController.bindApplicationsIfNeeded();
}
final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
@@ -79,6 +79,6 @@
}
});
}
- bindUpdatedWorkspaceItems(updatedWorkspaceItems);
+ taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems);
}
}
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 2457a42..d238213 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,12 +15,13 @@
*/
package com.android.launcher3.model;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -33,7 +34,7 @@
/**
* Handles changes due to a sessions updates for a currently installing app.
*/
-public class PackageInstallStateChangedTask extends BaseModelUpdateTask {
+public class PackageInstallStateChangedTask implements ModelUpdateTask {
@NonNull
private final PackageInstallInfo mInstallInfo;
@@ -43,16 +44,17 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
if (mInstallInfo.state == PackageInstallInfo.STATUS_INSTALLED) {
try {
// For instant apps we do not get package-add. Use setting events to update
// any pinned icons.
- ApplicationInfo ai = app.getContext()
+ Context context = taskController.getApp().getContext();
+ ApplicationInfo ai = context
.getPackageManager().getApplicationInfo(mInstallInfo.packageName, 0);
- if (InstantAppResolver.newInstance(app.getContext()).isInstantApp(ai)) {
- app.getModel().newModelCallbacks()
+ if (InstantAppResolver.newInstance(context).isInstantApp(ai)) {
+ taskController.getApp().getModel().newModelCallbacks()
.onPackageAdded(ai.packageName, mInstallInfo.user);
}
} catch (PackageManager.NameNotFoundException e) {
@@ -66,10 +68,11 @@
List<AppInfo> updatedAppInfos = apps.updatePromiseInstallInfo(mInstallInfo);
if (!updatedAppInfos.isEmpty()) {
for (AppInfo appInfo : updatedAppInfos) {
- scheduleCallbackTask(c -> c.bindIncrementalDownloadProgressUpdated(appInfo));
+ taskController.scheduleCallbackTask(
+ c -> c.bindIncrementalDownloadProgressUpdated(appInfo));
}
}
- bindApplicationsIfNeeded();
+ taskController.bindApplicationsIfNeeded();
}
synchronized (dataModel) {
@@ -90,7 +93,8 @@
}
if (!updates.isEmpty()) {
- scheduleCallbackTask(callbacks -> callbacks.bindRestoreItemsChange(updates));
+ taskController.scheduleCallbackTask(
+ callbacks -> callbacks.bindRestoreItemsChange(updates));
}
}
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 0ba468d..079987b 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -36,9 +36,9 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.FileLog;
@@ -48,7 +48,7 @@
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -71,11 +71,11 @@
* or when a user availability changes.
*/
@SuppressWarnings("NewApi")
-public class PackageUpdatedTask extends BaseModelUpdateTask {
+public class PackageUpdatedTask implements ModelUpdateTask {
// TODO(b/290090023): Set to false after root causing is done.
- private static final boolean DEBUG = true;
private static final String TAG = "PackageUpdatedTask";
+ private static final boolean DEBUG = true;
public static final int OP_NONE = 0;
public static final int OP_ADD = 1;
@@ -102,8 +102,9 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList appsList) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList appsList) {
+ final LauncherAppState app = taskController.getApp();
final Context context = app.getContext();
final IconCache iconCache = app.getIconCache();
@@ -116,29 +117,39 @@
: ItemInfoMatcher.ofPackages(packageSet, mUser);
final HashSet<ComponentName> removedComponents = new HashSet<>();
final HashMap<String, List<LauncherActivityInfo>> activitiesLists = new HashMap<>();
-
+ if (DEBUG) {
+ Log.d(TAG, "Package updated: mOp=" + getOpString()
+ + " packages=" + Arrays.toString(packages));
+ }
switch (mOp) {
case OP_ADD: {
for (int i = 0; i < N; i++) {
- if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
iconCache.updateIconsForPkg(packages[i], mUser);
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
+ if (DEBUG) {
+ Log.d(TAG, "OP_ADD: PROMISE_APPS_IN_ALL_APPS enabled:"
+ + " removing promise icon apps from package=" + packages[i]);
+ }
appsList.removePackage(packages[i], mUser);
}
- activitiesLists.put(
- packages[i], appsList.addPackage(context, packages[i], mUser));
+ activitiesLists.put(packages[i],
+ appsList.addPackage(context, packages[i], mUser));
}
flagOp = FlagOp.NO_OP.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
break;
}
case OP_UPDATE:
- try (SafeCloseable t =
- appsList.trackRemoves(a -> removedComponents.add(a.componentName))) {
+ try (SafeCloseable t = appsList.trackRemoves(a -> {
+ Log.d(TAG, "OP_UPDATE - AllAppsList.trackRemoves callback:"
+ + " removed component=" + a.componentName
+ + " id=" + a.id
+ + " Look for earlier AllAppsList logs to find more information.");
+ removedComponents.add(a.componentName);
+ })) {
for (int i = 0; i < N; i++) {
- if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
iconCache.updateIconsForPkg(packages[i], mUser);
- activitiesLists.put(
- packages[i], appsList.updatePackage(context, packages[i], mUser));
+ activitiesLists.put(packages[i],
+ appsList.updatePackage(context, packages[i], mUser));
}
}
// Since package was just updated, the target must be available now.
@@ -146,14 +157,15 @@
break;
case OP_REMOVE: {
for (int i = 0; i < N; i++) {
- FileLog.d(TAG, "Removing app icon: " + packages[i]);
iconCache.removeIconsForPkg(packages[i], mUser);
}
// Fall through
}
case OP_UNAVAILABLE:
for (int i = 0; i < N; i++) {
- if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
+ if (DEBUG) {
+ Log.d(TAG, getOpString() + ": removing package=" + packages[i]);
+ }
appsList.removePackage(packages[i], mUser);
}
flagOp = FlagOp.NO_OP.addFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
@@ -162,7 +174,6 @@
case OP_UNSUSPEND:
flagOp = FlagOp.NO_OP.setFlag(
WorkspaceItemInfo.FLAG_DISABLED_SUSPENDED, mOp == OP_SUSPEND);
- if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
appsList.updateDisabledFlags(matcher, flagOp);
break;
case OP_USER_AVAILABILITY_CHANGE: {
@@ -192,7 +203,7 @@
break;
}
- bindApplicationsIfNeeded();
+ taskController.bindApplicationsIfNeeded();
final IntSet removedShortcuts = new IntSet();
// Shortcuts to keep even if the corresponding app was removed
@@ -233,7 +244,16 @@
.query(ShortcutRequest.PINNED);
if (shortcut.isEmpty()) {
isTargetValid = false;
+ if (DEBUG) {
+ Log.d(TAG, "Pinned Shortcut not found for updated"
+ + " package=" + si.getTargetPackage());
+ }
} else {
+ if (DEBUG) {
+ Log.d(TAG, "Found pinned shortcut for updated"
+ + " package=" + si.getTargetPackage()
+ + ", isTargetValid=" + isTargetValid);
+ }
si.updateFromDeepShortcutInfo(shortcut.get(0), context);
infoUpdated = true;
}
@@ -241,6 +261,7 @@
isTargetValid = context.getSystemService(LauncherApps.class)
.isActivityEnabled(cn, mUser);
}
+
if (!isTargetValid && (si.hasStatusFlag(
FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)
|| si.isArchived())) {
@@ -248,12 +269,25 @@
infoUpdated = true;
} else if (si.hasPromiseIconUi()) {
removedShortcuts.add(si.id);
+ if (DEBUG) {
+ FileLog.w(TAG, "Removing restored shortcut promise icon"
+ + " that no longer points to valid component."
+ + " id=" + si.id
+ + ", package=" + si.getTargetPackage()
+ + ", status=" + si.status
+ + ", isArchived=" + si.isArchived());
+ }
return;
}
} else if (!isTargetValid) {
removedShortcuts.add(si.id);
- FileLog.e(TAG, "Restored shortcut no longer valid "
- + si.getIntent());
+ if (DEBUG) {
+ FileLog.w(TAG, "Removing shortcut that no longer points to"
+ + " valid component."
+ + " id=" + si.id
+ + " package=" + si.getTargetPackage()
+ + " status=" + si.status);
+ }
return;
} else {
si.status = WorkspaceItemInfo.DEFAULT;
@@ -268,6 +302,8 @@
if (isNewApkAvailable) {
List<LauncherActivityInfo> activities = activitiesLists.get(
packageName);
+ // TODO: See if we can migrate this to
+ // AppInfo#updateRuntimeFlagsForActivityTarget
si.setProgressLevel(
activities == null || activities.isEmpty()
? 100
@@ -276,7 +312,7 @@
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
// In case an app is archived, we need to make sure that archived state
// in WorkspaceItemInfo is refreshed.
- if (Utilities.enableSupportForArchiving() && !activities.isEmpty()) {
+ if (Flags.enableSupportForArchiving() && !activities.isEmpty()) {
boolean newArchivalState = activities.get(
0).getActivityInfo().isArchived;
if (newArchivalState != si.isArchived()) {
@@ -286,10 +322,8 @@
}
if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
if (activities != null && !activities.isEmpty()) {
- si.status = ApiWrapper
- .isNonResizeableActivity(activities.get(0))
- ? si.status | WorkspaceItemInfo.FLAG_NON_RESIZEABLE
- : si.status & ~WorkspaceItemInfo.FLAG_NON_RESIZEABLE;
+ si.setNonResizeable(ApiWrapper.INSTANCE.get(context)
+ .isNonResizeableActivity(activities.get(0)));
}
iconCache.getTitleAndIcon(si, si.usingLowResIcon());
infoUpdated = true;
@@ -307,7 +341,7 @@
updatedWorkspaceItems.add(si);
}
if (infoUpdated && si.id != ItemInfo.NO_ID) {
- getModelWriter().updateItemInDatabase(si);
+ taskController.getModelWriter().updateItemInDatabase(si);
}
});
@@ -326,20 +360,21 @@
widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
widgets.add(widgetInfo);
- getModelWriter().updateItemInDatabase(widgetInfo);
+ taskController.getModelWriter().updateItemInDatabase(widgetInfo);
}
}
}
- bindUpdatedWorkspaceItems(updatedWorkspaceItems);
+ taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems);
if (!removedShortcuts.isEmpty()) {
- deleteAndBindComponentsRemoved(
+ taskController.deleteAndBindComponentsRemoved(
ItemInfoMatcher.ofItemIds(removedShortcuts),
- "removed because the target component is invalid");
+ "removing shortcuts with invalid target components."
+ + " ids=" + removedShortcuts);
}
if (!widgets.isEmpty()) {
- scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
+ taskController.scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
}
}
@@ -347,6 +382,9 @@
if (mOp == OP_REMOVE) {
// Mark all packages in the broadcast to be removed
Collections.addAll(removedPackages, packages);
+ if (DEBUG) {
+ Log.d(TAG, "OP_REMOVE: removing packages=" + Arrays.toString(packages));
+ }
// No need to update the removedComponents as
// removedPackages is a super-set of removedComponents
@@ -355,24 +393,21 @@
final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
for (int i=0; i<N; i++) {
if (!launcherApps.isPackageEnabled(packages[i], mUser)) {
+ if (DEBUG) {
+ Log.d(TAG, "OP_UPDATE:"
+ + " package " + packages[i] + " is disabled, removing package.");
+ }
removedPackages.add(packages[i]);
}
}
}
if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
- // This predicate is used to mark an ItemInfo for removal if its package or component
- // is marked for removal.
- Predicate<ItemInfo> removeAppMatch =
+ Predicate<ItemInfo> removeMatch =
ItemInfoMatcher.ofPackages(removedPackages, mUser)
.or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
.and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate());
- // This predicate is used to mark an app pair for removal if it contains an app marked
- // for removal.
- Predicate<ItemInfo> removeAppPairMatch =
- ItemInfoMatcher.forAppPairMatch(removeAppMatch);
- Predicate<ItemInfo> removeMatch = removeAppMatch.or(removeAppPairMatch);
- deleteAndBindComponentsRemoved(removeMatch,
+ taskController.deleteAndBindComponentsRemoved(removeMatch,
"removed because the corresponding package or component is removed. "
+ "mOp=" + mOp + " removedPackages=" + removedPackages.stream().collect(
Collectors.joining(",", "[", "]"))
@@ -391,7 +426,7 @@
for (int i = 0; i < N; i++) {
dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
}
- bindUpdatedWidgets(dataModel);
+ taskController.bindUpdatedWidgets(dataModel);
}
}
@@ -407,7 +442,8 @@
return false;
}
// Try to find the best match activity.
- Intent intent = new PackageManagerHelper(context).getAppLaunchIntent(packageName, mUser);
+ Intent intent = PackageManagerHelper.INSTANCE.get(context)
+ .getAppLaunchIntent(packageName, mUser);
if (intent != null) {
si.intent = intent;
si.status = WorkspaceItemInfo.DEFAULT;
@@ -415,4 +451,18 @@
}
return false;
}
+
+ private String getOpString() {
+ return switch (mOp) {
+ case OP_NONE -> "NONE";
+ case OP_ADD -> "ADD";
+ case OP_UPDATE -> "UPDATE";
+ case OP_REMOVE -> "REMOVE";
+ case OP_UNAVAILABLE -> "UNAVAILABLE";
+ case OP_SUSPEND -> "SUSPEND";
+ case OP_UNSUSPEND -> "UNSUSPEND";
+ case OP_USER_AVAILABILITY_CHANGE -> "USER_AVAILABILITY_CHANGE";
+ default -> "UNKNOWN";
+ };
+ }
}
diff --git a/src/com/android/launcher3/model/ReloadStringCacheTask.java b/src/com/android/launcher3/model/ReloadStringCacheTask.java
index 34f7057..3d974d6 100644
--- a/src/com/android/launcher3/model/ReloadStringCacheTask.java
+++ b/src/com/android/launcher3/model/ReloadStringCacheTask.java
@@ -17,13 +17,13 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
/**
* Handles updates due to changes in Device Policy Management resources triggered by
* {@link android.app.admin.DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED}.
*/
-public class ReloadStringCacheTask extends BaseModelUpdateTask {
+public class ReloadStringCacheTask implements ModelUpdateTask {
@NonNull
private ModelDelegate mModelDelegate;
@@ -33,12 +33,12 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList appsList) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
synchronized (dataModel) {
mModelDelegate.loadStringCache(dataModel.stringCache);
StringCache cloneSC = dataModel.stringCache.clone();
- scheduleCallbackTask(c -> c.bindStringCache(cloneSC));
+ taskController.scheduleCallbackTask(c -> c.bindStringCache(cloneSC));
}
}
}
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
index 8cfa3aa..5293316 100644
--- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -52,7 +52,7 @@
@Override
public void onReceive(Context context, Intent intent) {
final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
- final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
+ final PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context);
for (PackageUserKey puk : mPackages) {
UserHandle user = puk.mUser;
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index a6a04a7..1916d23 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -38,7 +39,7 @@
/**
* Handles changes due to shortcut manager updates (deep shortcut changes)
*/
-public class ShortcutsChangedTask extends BaseModelUpdateTask {
+public class ShortcutsChangedTask implements ModelUpdateTask {
@NonNull
private final String mPackageName;
@@ -61,8 +62,9 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
+ 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<>();
@@ -78,10 +80,12 @@
if (!matchingWorkspaceItems.isEmpty()) {
if (mShortcuts.isEmpty()) {
+ PackageManagerHelper packageManagerHelper =
+ PackageManagerHelper.INSTANCE.get(context);
// Verify that the app is indeed installed.
- if (!new PackageManagerHelper(app.getContext())
- .isAppInstalled(mPackageName, mUser)) {
- // App is not installed, ignoring package events
+ if (!packageManagerHelper.isAppInstalled(mPackageName, mUser)
+ && !packageManagerHelper.isAppArchivedForUser(mPackageName, mUser)) {
+ // App is not installed or archived, ignoring package events
return;
}
}
@@ -113,9 +117,9 @@
});
}
- bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
+ taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
if (!nonPinnedIds.isEmpty()) {
- deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
+ taskController.deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
nonPinnedIds.stream()
.map(id -> new ShortcutKey(mPackageName, mUser, id))
.collect(Collectors.toSet())),
@@ -126,7 +130,7 @@
if (mUpdateIdMap) {
// Update the deep shortcut map if the list of ids has changed for an activity.
dataModel.updateDeepShortcutCounts(mPackageName, mUser, mShortcuts);
- bindDeepShortcuts(dataModel);
+ taskController.bindDeepShortcuts(dataModel);
}
}
}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 63ca35b..3dc5ff3 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -24,6 +24,7 @@
import androidx.annotation.NonNull;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -40,7 +41,7 @@
/**
* Task to handle changing of lock state of the user
*/
-public class UserLockStateChangedTask extends BaseModelUpdateTask {
+public class UserLockStateChangedTask implements ModelUpdateTask {
@NonNull
private final UserHandle mUser;
@@ -52,8 +53,9 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
+ LauncherAppState app = taskController.getApp();
Context context = app.getContext();
HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>();
@@ -98,9 +100,10 @@
}
});
}
- bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
+ taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
if (!removedKeys.isEmpty()) {
- deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(removedKeys),
+ taskController.deleteAndBindComponentsRemoved(
+ ItemInfoMatcher.ofShortcutKeys(removedKeys),
"removed during unlock because it's no longer available"
+ " (possibly due to clear data)");
}
@@ -118,6 +121,6 @@
null, mUser,
new ShortcutRequest(context, mUser).query(ShortcutRequest.ALL));
}
- bindDeepShortcuts(dataModel);
+ taskController.bindDeepShortcuts(dataModel);
}
}
diff --git a/src/com/android/launcher3/model/UserManagerState.java b/src/com/android/launcher3/model/UserManagerState.java
index 720f08e..ed32430 100644
--- a/src/com/android/launcher3/model/UserManagerState.java
+++ b/src/com/android/launcher3/model/UserManagerState.java
@@ -17,6 +17,7 @@
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseBooleanArray;
@@ -27,6 +28,8 @@
*/
public class UserManagerState {
+ private static final String TAG = "UserManagerState";
+
public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
private final LongSparseArray<Boolean> mQuietUsersSerialNoMap = new LongSparseArray<>();
@@ -39,6 +42,13 @@
for (UserHandle user : userManager.getUserProfiles()) {
long serialNo = userCache.getSerialNumberForUser(user);
boolean isUserQuiet = userManager.isQuietModeEnabled(user);
+ // Mapping different UserHandles to the same serialNo in allUsers could lead to losing
+ // UserHandle and cause a series of problems, such as incorrectly marking app as
+ // disabled and deleting app icons from workspace.
+ if (allUsers.get(serialNo) != null) {
+ Log.w(TAG, String.format("Override allUsers[%d]=%s with %s",
+ serialNo, allUsers.get(serialNo), user));
+ }
allUsers.put(serialNo, user);
mQuietUsersHashCodeMap.put(user.hashCode(), isUserQuiet);
mQuietUsersSerialNoMap.put(serialNo, isUserQuiet);
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index 1dd58c3..3f88717 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -123,6 +123,7 @@
if (!Flags.enableGeneratedPreviews() || generatedPreviews == null) {
return false;
}
- return generatedPreviews.contains(widgetCategory);
+ return generatedPreviews.contains(widgetCategory)
+ && generatedPreviews.get(widgetCategory) != null;
}
}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
similarity index 80%
rename from src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
rename to src/com/android/launcher3/model/WidgetsModel.java
index 8b983fc..454ae96 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -3,6 +3,7 @@
import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
@@ -31,6 +32,7 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
@@ -40,10 +42,12 @@
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.wm.shell.Flags;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -59,10 +63,6 @@
*/
public class WidgetsModel {
- // True is the widget support is disabled.
- public static final boolean GO_DISABLE_WIDGETS = false;
- public static final boolean GO_DISABLE_NOTIFICATION_DOTS = false;
-
private static final String TAG = "WidgetsModel";
private static final boolean DEBUG = false;
@@ -79,6 +79,9 @@
public synchronized ArrayList<WidgetsListBaseEntry> getFilteredWidgetsListForPicker(
Context context,
Predicate<WidgetItem> widgetItemFilter) {
+ if (!WIDGETS_ENABLED) {
+ return new ArrayList<>();
+ }
ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
@@ -111,11 +114,14 @@
/** Returns a mapping of packages to their widgets without static shortcuts. */
public synchronized Map<PackageUserKey, List<WidgetItem>> getAllWidgetsWithoutShortcuts() {
+ if (!WIDGETS_ENABLED) {
+ return Collections.emptyMap();
+ }
Map<PackageUserKey, List<WidgetItem>> packagesToWidgets = new HashMap<>();
mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) -> {
List<WidgetItem> widgets = widgetsAndShortcuts.stream()
- .filter(item -> item.widgetInfo != null)
- .collect(toList());
+ .filter(item -> item.widgetInfo != null)
+ .collect(toList());
if (widgets.size() > 0) {
packagesToWidgets.put(
new PackageUserKey(packageItemInfo.packageName, packageItemInfo.user),
@@ -126,11 +132,30 @@
}
/**
+ * Returns a map of widget component keys to corresponding widget items. Excludes the
+ * shortcuts.
+ */
+ public synchronized Map<ComponentKey, WidgetItem> getAllWidgetComponentsWithoutShortcuts() {
+ if (!WIDGETS_ENABLED) {
+ return Collections.emptyMap();
+ }
+ Map<ComponentKey, WidgetItem> widgetsMap = new HashMap<>();
+ mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) ->
+ widgetsAndShortcuts.stream().filter(item -> item.widgetInfo != null).forEach(
+ item -> widgetsMap.put(new ComponentKey(item.componentName, item.user),
+ item)));
+ return widgetsMap;
+ }
+
+ /**
* @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
* only widgets and shortcuts associated with the package/user are.
*/
public List<ComponentWithLabelAndIcon> update(
LauncherAppState app, @Nullable PackageUserKey packageUser) {
+ if (!WIDGETS_ENABLED) {
+ return Collections.emptyList();
+ }
Preconditions.assertWorkerThread();
Context context = app.getContext();
@@ -194,6 +219,7 @@
// add and update.
mWidgetsList.putAll(rawWidgetsShortcuts.stream()
.filter(new WidgetValidityCheck(app))
+ .filter(new WidgetFlagCheck())
.flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
.map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
.collect(groupingBy(pair -> pair.first, mapping(pair -> pair.second, toList()))));
@@ -207,6 +233,9 @@
public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
LauncherAppState app) {
+ if (!WIDGETS_ENABLED) {
+ return;
+ }
WidgetManagerHelper widgetManager = new WidgetManagerHelper(app.getContext());
for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
if (packageNames.contains(entry.getKey().packageName)) {
@@ -229,17 +258,45 @@
}
}
+ private PackageItemInfo createPackageItemInfo(
+ ComponentName providerName,
+ UserHandle user,
+ int category
+ ) {
+ if (category == NO_CATEGORY) {
+ return new PackageItemInfo(providerName.getPackageName(), user);
+ } else {
+ return new PackageItemInfo("" , category, user);
+ }
+ }
+
+ private IntSet getCategories(ComponentName providerName, Context context) {
+ IntSet categories = WidgetSections.getWidgetsToCategory(context).get(providerName);
+ if (categories != null) {
+ return categories;
+ }
+ categories = new IntSet();
+ categories.add(NO_CATEGORY);
+ return categories;
+ }
+
public WidgetItem getWidgetProviderInfoByProviderName(
- ComponentName providerName, UserHandle user) {
- List<WidgetItem> widgetsList = mWidgetsList.get(
- new PackageItemInfo(providerName.getPackageName(), user));
- if (widgetsList == null) {
+ ComponentName providerName, UserHandle user, Context context) {
+ if (!WIDGETS_ENABLED) {
return null;
}
+ IntSet categories = getCategories(providerName, context);
- for (WidgetItem item : widgetsList) {
- if (item.componentName.equals(providerName)) {
- return item;
+ // Checking if we have a provider in any of the categories.
+ for (Integer category: categories) {
+ PackageItemInfo key = createPackageItemInfo(providerName, user, category);
+ List<WidgetItem> widgets = mWidgetsList.get(key);
+ if (widgets != null) {
+ return widgets.stream().filter(
+ item -> item.componentName.equals(providerName)
+ )
+ .findFirst()
+ .orElse(null);
}
}
return null;
@@ -322,6 +379,21 @@
}
}
+ private static class WidgetFlagCheck implements Predicate<WidgetItem> {
+
+ private static final String BUBBLES_SHORTCUT_WIDGET =
+ "com.android.systemui/com.android.wm.shell.bubbles.shortcut"
+ + ".CreateBubbleShortcutActivity";
+
+ @Override
+ public boolean test(WidgetItem widgetItem) {
+ if (BUBBLES_SHORTCUT_WIDGET.equals(widgetItem.componentName.flattenToString())) {
+ return Flags.enableRetrievableBubbles();
+ }
+ return true;
+ }
+ }
+
private static final class PackageItemInfoCache {
private final Map<PackageUserKey, PackageItemInfo> mMap = new ArrayMap<>();
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 59f56df..90e47d6 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -26,19 +26,25 @@
import android.text.TextUtils
import android.util.Log
import android.util.LongSparseArray
+import com.android.launcher3.Flags
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.Utilities
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
+import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.logging.FileLog
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.AppPairInfo
+import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.IconRequestInfo
import com.android.launcher3.model.data.ItemInfoWithIcon
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.pm.PackageInstallInfo
+import com.android.launcher3.pm.UserCache
import com.android.launcher3.shortcuts.ShortcutKey
-import com.android.launcher3.uioverrides.ApiWrapper
+import com.android.launcher3.util.ApiWrapper
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
@@ -56,6 +62,7 @@
class WorkspaceItemProcessor(
private val c: LoaderCursor,
private val memoryLogger: LoaderMemoryLogger?,
+ private val userCache: UserCache,
private val userManagerState: UserManagerState,
private val launcherApps: LauncherApps,
private val pendingPackages: MutableSet<PackageUserKey>,
@@ -177,6 +184,9 @@
}
}
}
+ if (intent.`package` == null) {
+ intent.`package` = targetPkg
+ }
// else if cn == null => can't infer much, leave it
// else if !validPkg => could be restored icon or missing sd-card
when {
@@ -322,17 +332,17 @@
}
val activityInfo = c.launcherActivityInfo
if (activityInfo != null) {
- if (ApiWrapper.isNonResizeableActivity(activityInfo)) {
- info.status = info.status or WorkspaceItemInfo.FLAG_NON_RESIZEABLE
- }
- info.setProgressLevel(
- PackageManagerHelper.getLoadingProgress(activityInfo),
- PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING
+ AppInfo.updateRuntimeFlagsForActivityTarget(
+ info,
+ activityInfo,
+ userCache.getUserInfo(c.user),
+ ApiWrapper.INSTANCE[app.context],
+ pmHelper
)
}
if (
(c.restoreFlag != 0 ||
- Utilities.enableSupportForArchiving() &&
+ Flags.enableSupportForArchiving() &&
activityInfo != null &&
activityInfo.applicationInfo.isArchived) && !TextUtils.isEmpty(targetPkg)
) {
@@ -344,7 +354,7 @@
ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE.inv()
} else if (
activityInfo == null ||
- (Utilities.enableSupportForArchiving() &&
+ (Flags.enableSupportForArchiving() &&
activityInfo.applicationInfo.isArchived)
) {
// For archived apps, include progress info in case there is
@@ -360,25 +370,46 @@
}
/**
- * Loads the folder information from the database and formats it into a FolderInfo. Some of the
- * processing for folder content items is done in LoaderTask after all the items in the
- * workspace have been loaded. The loaded FolderInfos are stored in the BgDataModel.
+ * Loads CollectionInfo information from the database and formats it. This function runs while
+ * LoaderTask is still active; some of the processing for folder content items is done after all
+ * the items in the workspace have been loaded. The loaded and formatted CollectionInfo is then
+ * stored in the BgDataModel.
*/
private fun processFolderOrAppPair() {
- val folderInfo =
- bgDataModel.findOrMakeFolder(c.id).apply {
- c.applyCommonProperties(this)
- itemType = c.itemType
- // Do not trim the folder label, as is was set by the user.
- title = c.getString(c.mTitleIndex)
- spanX = 1
- spanY = 1
- options = c.options
+ var collection = bgDataModel.findOrMakeFolder(c.id)
+ // 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) {
+ if (!FeatureFlags.enableAppPairs()) {
+ // If app pairs are not enabled, stop loading.
+ Log.e(TAG, "app pairs flag is off, did not load app pair")
+ return
}
- // no special handling required for restored folders
+ val folderInfo: FolderInfo = collection
+ val newAppPair = AppPairInfo()
+ // Move the placeholder's contents over to the new app pair.
+ folderInfo.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)
+ // Do not trim the folder label, as is was set by the user.
+ collection.title = c.getString(c.mTitleIndex)
+ collection.spanX = 1
+ collection.spanY = 1
+ if (collection is FolderInfo) {
+ collection.options = c.options
+ } else {
+ // An app pair may be inside another folder, so it needs to preserve rank information.
+ collection.rank = c.rank
+ }
+
c.markRestored()
- c.checkAndAddItem(folderInfo, bgDataModel, memoryLogger)
+ c.checkAndAddItem(collection, bgDataModel, memoryLogger)
}
/**
@@ -410,14 +441,21 @@
appWidgetInfo.restoreStatus = c.restoreFlag
if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) {
c.markDeleted(
- "Widget has invalid size: ${appWidgetInfo.spanX}x${appWidgetInfo.spanY}",
+ "processWidget: Widget has invalid size: ${appWidgetInfo.spanX}x${appWidgetInfo.spanY}" +
+ ", id=${c.id}," +
+ ", appWidgetId=${c.appWidgetId}," +
+ ", component=${component}",
RestoreError.INVALID_LOCATION
)
return
}
if (!c.isOnWorkspaceOrHotseat) {
c.markDeleted(
- "Widget found where container != CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!",
+ "processWidget: invalid Widget container != CONTAINER_DESKTOP nor CONTAINER_HOTSEAT." +
+ " id=${c.id}," +
+ ", appWidgetId=${c.appWidgetId}," +
+ ", component=${component}," +
+ ", container=${c.container}",
RestoreError.INVALID_LOCATION
)
return
@@ -428,7 +466,12 @@
val inflationResult = widgetInflater.inflateAppWidget(appWidgetInfo)
var shouldUpdate = inflationResult.isUpdate
val lapi = inflationResult.widgetInfo
-
+ FileLog.d(
+ TAG,
+ "processWidget: id=${c.id}" +
+ ", appWidgetId=${c.appWidgetId}" +
+ ", inflationResult=$inflationResult"
+ )
when (inflationResult.type) {
WidgetInflater.TYPE_DELETE -> {
c.markDeleted(inflationResult.reason, inflationResult.restoreErrorType)
@@ -443,12 +486,16 @@
!isSafeMode &&
(si == null) &&
(lapi == null) &&
- !(Utilities.enableSupportForArchiving() &&
+ !(Flags.enableSupportForArchiving() &&
pmHelper.isAppArchived(component.packageName))
) {
// Restore never started
c.markDeleted(
- "Unrestored widget removed: $component",
+ "processWidget: Unrestored Pending widget removed:" +
+ " id=${c.id}" +
+ ", appWidgetId=${c.appWidgetId}" +
+ ", component=${component}" +
+ ", restoreFlag:=${c.restoreFlag}",
RestoreError.APP_NOT_INSTALLED
)
return
@@ -491,7 +538,10 @@
if (appWidgetInfo.spanX < lapi.minSpanX || appWidgetInfo.spanY < lapi.minSpanY) {
FileLog.d(
TAG,
- "Widget ${lapi.component} minSizes not meet: span=${appWidgetInfo.spanX}x${appWidgetInfo.spanY} minSpan=${lapi.minSpanX}x${lapi.minSpanY}"
+ " processWidget: Widget ${lapi.component} minSizes not met: span=${appWidgetInfo.spanX}x${appWidgetInfo.spanY} minSpan=${lapi.minSpanX}x${lapi.minSpanY}," +
+ " id: ${c.id}," +
+ " appWidgetId: ${c.appWidgetId}," +
+ " component=${component}"
)
logWidgetInfo(app.invariantDeviceProfile, lapi)
}
diff --git a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
index 929f698..1a6d178 100644
--- a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
+++ b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
@@ -15,8 +15,8 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import android.util.LongSparseArray;
@@ -68,7 +68,7 @@
// First check the preferred screen.
IntSet screensToExclude = new IntSet();
if (FeatureFlags.QSB_ON_FIRST_SCREEN
- && !shouldShowFirstPageWidget()) {
+ && !SHOULD_SHOW_FIRST_PAGE_WIDGET) {
screensToExclude.add(FIRST_SCREEN_ID);
}
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 210d720..a4281f8 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -35,6 +35,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.UserIconInfo;
@@ -89,10 +90,12 @@
*/
public AppInfo(Context context, LauncherActivityInfo info, UserHandle user) {
this(info, UserCache.INSTANCE.get(context).getUserInfo(user),
+ ApiWrapper.INSTANCE.get(context), PackageManagerHelper.INSTANCE.get(context),
context.getSystemService(UserManager.class).isQuietModeEnabled(user));
}
- public AppInfo(LauncherActivityInfo info, UserIconInfo userIconInfo, boolean quietModeEnabled) {
+ public AppInfo(LauncherActivityInfo info, UserIconInfo userIconInfo,
+ ApiWrapper apiWrapper, PackageManagerHelper pmHelper, boolean quietModeEnabled) {
this.componentName = info.getComponentName();
this.container = CONTAINER_ALL_APPS;
this.user = userIconInfo.user;
@@ -102,7 +105,7 @@
runtimeStatusFlags |= FLAG_DISABLED_QUIET_USER;
}
uid = info.getApplicationInfo().uid;
- updateRuntimeFlagsForActivityTarget(this, info, userIconInfo);
+ updateRuntimeFlagsForActivityTarget(this, info, userIconInfo, apiWrapper, pmHelper);
}
public AppInfo(AppInfo info) {
@@ -175,14 +178,27 @@
return componentName;
}
- public static void updateRuntimeFlagsForActivityTarget(
- ItemInfoWithIcon info, LauncherActivityInfo lai, UserIconInfo userIconInfo) {
+ /**
+ * Updates the runtime status flags for the given info based on the state of the specified
+ * activity.
+ */
+ public static boolean updateRuntimeFlagsForActivityTarget(
+ ItemInfoWithIcon info, LauncherActivityInfo lai, UserIconInfo userIconInfo,
+ ApiWrapper apiWrapper, PackageManagerHelper pmHelper) {
+ final int oldProgressLevel = info.getProgressLevel();
+ final int oldRuntimeStatusFlags = info.runtimeStatusFlags;
ApplicationInfo appInfo = lai.getApplicationInfo();
if (PackageManagerHelper.isAppSuspended(appInfo)) {
info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
+ } else {
+ info.runtimeStatusFlags &= ~FLAG_DISABLED_SUSPENDED;
}
- if (Utilities.enableSupportForArchiving() && lai.getActivityInfo().isArchived) {
- info.runtimeStatusFlags |= FLAG_ARCHIVED;
+ if (Flags.enableSupportForArchiving()) {
+ if (lai.getActivityInfo().isArchived) {
+ info.runtimeStatusFlags |= FLAG_ARCHIVED;
+ } else {
+ info.runtimeStatusFlags &= ~FLAG_ARCHIVED;
+ }
}
info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
@@ -190,6 +206,8 @@
if (Flags.privateSpaceRestrictAccessibilityDrag()) {
if (userIconInfo.isPrivate()) {
info.runtimeStatusFlags |= FLAG_NOT_PINNABLE;
+ } else {
+ info.runtimeStatusFlags &= ~FLAG_NOT_PINNABLE;
}
}
@@ -197,6 +215,11 @@
info.setProgressLevel(
PackageManagerHelper.getLoadingProgress(lai),
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
+ info.setNonResizeable(apiWrapper.isNonResizeableActivity(lai));
+ info.setSupportsMultiInstance(
+ pmHelper.supportsMultiInstance(lai.getComponentName()));
+ return (oldProgressLevel != info.getProgressLevel())
+ || (oldRuntimeStatusFlags != info.runtimeStatusFlags);
}
@Override
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
new file mode 100644
index 0000000..2eb6154
--- /dev/null
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model.data
+
+import android.content.Context
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.R
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.views.ActivityContext
+
+/** A type of app collection that launches multiple apps into split screen. */
+class AppPairInfo() : CollectionInfo() {
+ private var contents: ArrayList<WorkspaceItemInfo> = ArrayList()
+
+ init {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
+ }
+
+ /** Convenience constructor, calls primary constructor and init block */
+ constructor(app1: WorkspaceItemInfo, app2: WorkspaceItemInfo) : this() {
+ add(app1)
+ add(app2)
+ }
+
+ /** Creates a new AppPairInfo that is a copy of the provided one. */
+ constructor(appPairInfo: AppPairInfo) : this() {
+ contents = appPairInfo.contents.clone() as ArrayList<WorkspaceItemInfo>
+ copyFrom(appPairInfo)
+ }
+
+ /** Adds an element to the contents ArrayList. */
+ override fun add(item: ItemInfo) {
+ if (item !is WorkspaceItemInfo) {
+ throw RuntimeException("tried to add an illegal type into an app pair")
+ }
+
+ contents.add(item)
+ }
+
+ /** Returns the app pair's member apps as an ArrayList of [ItemInfo]. */
+ override fun getContents(): ArrayList<ItemInfo> =
+ ArrayList(contents.stream().map { it as ItemInfo }.toList())
+
+ /** Returns the app pair's member apps as an ArrayList of [WorkspaceItemInfo]. */
+ override fun getAppContents(): ArrayList<WorkspaceItemInfo> = contents
+
+ /** Returns the first app in the pair. */
+ fun getFirstApp() = contents[0]
+
+ /** Returns the second app in the pair. */
+ fun getSecondApp() = contents[1]
+
+ /** Returns if either of the app pair members is currently disabled. */
+ override fun isDisabled() = anyMatch { it.isDisabled }
+
+ /** Checks if member apps are launchable at the current screen size. */
+ fun isLaunchable(context: Context): Pair<Boolean, Boolean> {
+ val isTablet =
+ (ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet
+ return Pair(
+ isTablet || !getFirstApp().isNonResizeable(),
+ isTablet || !getSecondApp().isNonResizeable()
+ )
+ }
+
+ /** Fetches high-res icons for member apps if needed. */
+ fun fetchHiResIconsIfNeeded(iconCache: IconCache) {
+ getAppContents().stream().filter(ItemInfoWithIcon::usingLowResIcon).forEach { member ->
+ iconCache.getTitleAndIcon(member, false)
+ }
+ }
+
+ /**
+ * App pairs will report itself as "disabled" (for accessibility) if either of the following is
+ * true:
+ * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is paused
+ * or can't be launched for some other reason).
+ * 2) One of the member apps can't be launched due to screen size requirements.
+ */
+ fun shouldReportDisabled(context: Context): Boolean {
+ return isDisabled || !isLaunchable(context).first || !isLaunchable(context).second
+ }
+
+ /** Generates a default title for the app pair and sets it. */
+ fun generateTitle(context: Context): CharSequence? {
+ val app1: CharSequence? = getFirstApp().title
+ val app2: CharSequence? = getSecondApp().title
+ title = context.getString(R.string.app_pair_default_title, app1, app2)
+ return title
+ }
+
+ /** Generates an ItemInfo for logging. */
+ override fun buildProto(cInfo: CollectionInfo?): LauncherAtom.ItemInfo {
+ val appPairIcon = LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size)
+ appPairIcon.setLabelInfo(title.toString())
+ return getDefaultItemInfoBuilder()
+ .setFolderIcon(appPairIcon)
+ .setRank(rank)
+ .setContainerInfo(getContainerInfo())
+ .build()
+ }
+}
diff --git a/src/com/android/launcher3/model/data/CollectionInfo.kt b/src/com/android/launcher3/model/data/CollectionInfo.kt
new file mode 100644
index 0000000..4f5e12f
--- /dev/null
+++ b/src/com/android/launcher3/model/data/CollectionInfo.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model.data
+
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.util.ContentWriter
+import java.util.function.Predicate
+
+abstract class CollectionInfo : ItemInfo() {
+ /** Adds an ItemInfo to the collection. Throws if given an illegal type. */
+ abstract fun add(item: ItemInfo)
+
+ /** Returns the collection's contents as an ArrayList of [ItemInfo]. */
+ abstract fun getContents(): ArrayList<ItemInfo>
+
+ /**
+ * Returns the collection's contents as an ArrayList of [WorkspaceItemInfo]. Does not include
+ * other collection [ItemInfo]s that are inside this collection; rather, it should collect
+ * *their* contents and adds them to the ArrayList.
+ */
+ abstract fun getAppContents(): ArrayList<WorkspaceItemInfo>
+
+ /** Convenience function. Checks contents to see if any match a given predicate. */
+ fun anyMatch(matcher: Predicate<ItemInfo>) = getContents().stream().anyMatch(matcher)
+
+ override fun onAddToDatabase(writer: ContentWriter) {
+ super.onAddToDatabase(writer)
+ writer.put(LauncherSettings.Favorites.TITLE, title)
+ }
+
+ /** Returns the collection wrapped as {@link LauncherAtom.ItemInfo} for logging. */
+ override fun buildProto(): LauncherAtom.ItemInfo {
+ return buildProto(null)
+ }
+}
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 83ba2b3..18d2b85 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -24,13 +24,12 @@
import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL;
import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL;
-import android.os.Process;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
+import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderNameInfos;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.Attribute;
@@ -49,7 +48,7 @@
/**
* Represents a folder containing shortcuts or apps.
*/
-public class FolderInfo extends ItemInfo {
+public class FolderInfo extends CollectionInfo {
public static final int NO_FLAGS = 0x00000000;
@@ -103,24 +102,18 @@
/**
* The apps and shortcuts
*/
- public ArrayList<WorkspaceItemInfo> contents = new ArrayList<>();
+ private final ArrayList<ItemInfo> contents = new ArrayList<>();
private ArrayList<FolderListener> mListeners = new ArrayList<>();
public FolderInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
- user = Process.myUserHandle();
}
- /**
- * Create an app pair, a type of app collection that launches multiple apps into split screen
- */
- public static FolderInfo createAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
- FolderInfo newAppPair = new FolderInfo();
- newAppPair.itemType = LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
- newAppPair.add(app1, /* animate */ false);
- newAppPair.add(app2, /* animate */ false);
- return newAppPair;
+ /** Adds a app or shortcut to the contents ArrayList without animation. */
+ @Override
+ public void add(@NonNull ItemInfo item) {
+ add(item, false /* animate */);
}
/**
@@ -128,16 +121,20 @@
*
* @param item
*/
- public void add(WorkspaceItemInfo item, boolean animate) {
- add(item, contents.size(), animate);
+ public void add(ItemInfo item, boolean animate) {
+ add(item, getContents().size(), animate);
}
/**
* Add an app or shortcut for a specified rank.
*/
- public void add(WorkspaceItemInfo item, int rank, boolean animate) {
- rank = Utilities.boundToRange(rank, 0, contents.size());
- contents.add(rank, item);
+ public void add(ItemInfo item, int rank, boolean animate) {
+ if (!Folder.willAccept(item)) {
+ throw new RuntimeException("tried to add an illegal type into a folder");
+ }
+
+ rank = Utilities.boundToRange(rank, 0, getContents().size());
+ getContents().add(rank, item);
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onAdd(item, rank);
}
@@ -149,14 +146,14 @@
*
* @param item
*/
- public void remove(WorkspaceItemInfo item, boolean animate) {
+ public void remove(ItemInfo item, boolean animate) {
removeAll(Collections.singletonList(item), animate);
}
/**
* Remove all matching app or shortcut. Does not change the DB.
*/
- public void removeAll(List<WorkspaceItemInfo> items, boolean animate) {
+ public void removeAll(List<ItemInfo> items, boolean animate) {
contents.removeAll(items);
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onRemove(items);
@@ -164,11 +161,38 @@
itemsChanged(animate);
}
+ /**
+ * Returns the folder's contents as an ArrayList of {@link ItemInfo}. Includes
+ * {@link WorkspaceItemInfo} and {@link AppPairInfo}s.
+ */
+ @NonNull
+ @Override
+ public ArrayList<ItemInfo> getContents() {
+ return contents;
+ }
+
+ /**
+ * Returns the folder's contents as an ArrayList of {@link WorkspaceItemInfo}. Note: Does not
+ * return any {@link AppPairInfo}s contained in the folder, instead collects *their* contents
+ * and adds them to the ArrayList.
+ */
+ @Override
+ public ArrayList<WorkspaceItemInfo> getAppContents() {
+ ArrayList<WorkspaceItemInfo> workspaceItemInfos = new ArrayList<>();
+ for (ItemInfo item : contents) {
+ if (item instanceof WorkspaceItemInfo wii) {
+ workspaceItemInfos.add(wii);
+ } else if (item instanceof AppPairInfo api) {
+ workspaceItemInfos.addAll(api.getAppContents());
+ }
+ }
+ return workspaceItemInfos;
+ }
+
@Override
public void onAddToDatabase(@NonNull ContentWriter writer) {
super.onAddToDatabase(writer);
- writer.put(LauncherSettings.Favorites.TITLE, title)
- .put(LauncherSettings.Favorites.OPTIONS, options);
+ writer.put(LauncherSettings.Favorites.OPTIONS, options);
}
public void addListener(FolderListener listener) {
@@ -186,9 +210,11 @@
}
public interface FolderListener {
- void onAdd(WorkspaceItemInfo item, int rank);
- void onRemove(List<WorkspaceItemInfo> item);
+ void onAdd(ItemInfo item, int rank);
+ void onRemove(List<ItemInfo> item);
void onItemsChanged(boolean animate);
+ void onTitleChanged(CharSequence title);
+
}
public boolean hasOption(int optionFlag) {
@@ -219,9 +245,9 @@
@NonNull
@Override
- public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo fInfo) {
+ public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo cInfo) {
FolderIcon.Builder folderIcon = FolderIcon.newBuilder()
- .setCardinality(contents.size());
+ .setCardinality(getContents().size());
if (LabelState.SUGGESTED.equals(getLabelState())) {
folderIcon.setLabelInfo(title.toString());
}
@@ -261,6 +287,10 @@
if (modelWriter != null) {
modelWriter.updateItemInDatabase(this);
}
+
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onTitleChanged(title);
+ }
}
/**
@@ -278,17 +308,15 @@
public ItemInfo makeShallowCopy() {
FolderInfo folderInfo = new FolderInfo();
folderInfo.copyFrom(this);
- folderInfo.contents = this.contents;
return folderInfo;
}
- /**
- * Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging.
- */
- @NonNull
@Override
- public LauncherAtom.ItemInfo buildProto() {
- return buildProto(null);
+ public void copyFrom(@NonNull ItemInfo info) {
+ super.copyFrom(info);
+ if (info instanceof FolderInfo fi) {
+ contents.addAll(fi.getContents());
+ }
}
/**
@@ -371,13 +399,4 @@
}
return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
}
-
- @Override
- public boolean isDisabled() {
- if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
- return contents.stream().anyMatch((WorkspaceItemInfo::isDisabled));
- }
-
- return super.isDisabled();
- }
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 55849c2..b82d0a0 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -73,6 +73,7 @@
* Represents an item in the launcher.
*/
public class ItemInfo {
+ private static final String TAG = "ItemInfo";
public static final boolean DEBUG = false;
public static final int NO_ID = -1;
@@ -94,6 +95,10 @@
* {@link Favorites#ITEM_TYPE_APP_PAIR},
* {@link Favorites#ITEM_TYPE_APPWIDGET} or
* {@link Favorites#ITEM_TYPE_CUSTOM_APPWIDGET}.
+ * {@link Favorites#ITEM_TYPE_TASK}.
+ * {@link Favorites#ITEM_TYPE_QSB}.
+ * {@link Favorites#ITEM_TYPE_SEARCH_ACTION}.
+ * {@link Favorites#ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON}.
*/
public int itemType;
@@ -281,7 +286,7 @@
@Override
@NonNull
public final String toString() {
- return getClass().getSimpleName() + "(" + dumpProperties() + ")";
+ return TAG + "(" + dumpProperties() + ")";
}
@NonNull
@@ -345,10 +350,9 @@
/**
* Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
- * @param fInfo
*/
@NonNull
- public LauncherAtom.ItemInfo buildProto(@Nullable final FolderInfo fInfo) {
+ public LauncherAtom.ItemInfo buildProto(@Nullable final CollectionInfo cInfo) {
LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder();
Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent());
switch (itemType) {
@@ -394,21 +398,21 @@
default:
break;
}
- if (fInfo != null) {
+ if (cInfo != null) {
LauncherAtom.FolderContainer.Builder folderBuilder =
LauncherAtom.FolderContainer.newBuilder();
folderBuilder.setGridX(cellX).setGridY(cellY).setPageIndex(screenId);
- switch (fInfo.container) {
+ switch (cInfo.container) {
case CONTAINER_HOTSEAT:
case CONTAINER_HOTSEAT_PREDICTION:
folderBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
- .setIndex(fInfo.screenId));
+ .setIndex(cInfo.screenId));
break;
case CONTAINER_DESKTOP:
folderBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
- .setPageIndex(fInfo.screenId)
- .setGridX(fInfo.cellX).setGridY(fInfo.cellY));
+ .setPageIndex(cInfo.screenId)
+ .setGridX(cInfo.cellX).setGridY(cInfo.cellY));
break;
}
itemBuilder.setContainerInfo(ContainerInfo.newBuilder().setFolder(folderBuilder));
@@ -424,12 +428,10 @@
@NonNull
protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() {
LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
- UserIconInfo info = getUserInfo();
- itemBuilder.setIsWork(info != null && info.isWork());
- itemBuilder.setUserType(getUserType(info));
- SettingsCache settingsCache = SettingsCache.INSTANCE.getNoCreate();
- boolean isKidsMode = settingsCache != null && settingsCache.getValue(NAV_BAR_KIDS_MODE, 0);
- itemBuilder.setIsKidsMode(isKidsMode);
+ SettingsCache.INSTANCE.executeIfCreated(cache ->
+ itemBuilder.setIsKidsMode(cache.getValue(NAV_BAR_KIDS_MODE, 0)));
+ UserCache.INSTANCE.executeIfCreated(cache ->
+ itemBuilder.setUserType(getUserType(cache.getUserInfo(user))));
itemBuilder.setRank(rank);
return itemBuilder;
}
@@ -523,15 +525,6 @@
this.title = title;
}
- private UserIconInfo getUserInfo() {
- UserCache userCache = UserCache.INSTANCE.getNoCreate();
- if (userCache == null) {
- return null;
- }
-
- return userCache.getUserInfo(user);
- }
-
private int getUserType(UserIconInfo info) {
if (info == null) {
return SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_UNKNOWN;
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 352c363..6ac44ff 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -22,13 +22,14 @@
import androidx.annotation.Nullable;
+import com.android.launcher3.Flags;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.pm.PackageInstallInfo;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
/**
* Represents an ItemInfo which also holds an icon.
@@ -121,9 +122,14 @@
public static final int FLAG_ARCHIVED = 1 << 14;
/**
- * Flag indicating it's the Private Space Install App icon.
+ * Flag indicating whether the package related to the item & user does not support resizing.
*/
- public static final int FLAG_PRIVATE_SPACE_INSTALL_APP = 1 << 15;
+ public static final int FLAG_NOT_RESIZEABLE = 1 << 15;
+
+ /**
+ * Flag indicating whether the package related to the item & user supports multiple instances.
+ */
+ public static final int FLAG_SUPPORTS_MULTI_INSTANCE = 1 << 16;
/**
* Status associated with the system state of the underlying item. This is calculated every
@@ -160,10 +166,6 @@
* and its install session is active
*/
public boolean isPendingDownload() {
- if (isArchived()) {
- return this.getProgressLevel() == 0
- && (this.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0;
- }
return getProgressLevel() == 0;
}
@@ -171,12 +173,17 @@
* Returns true if the app corresponding to the item is archived.
*/
public boolean isArchived() {
- if (!Utilities.enableSupportForArchiving()) {
+ if (!Flags.enableSupportForArchiving()) {
return false;
}
return (runtimeStatusFlags & FLAG_ARCHIVED) != 0;
}
+ /** Returns true if the app is archived and doesn't have an active install session. */
+ public boolean isInactiveArchive() {
+ return isArchived() && (runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) == 0;
+ }
+
/**
* Indicates whether we're using a low res icon
*/
@@ -249,14 +256,50 @@
}
}
+ /**
+ * Sets whether this app info supports multi-instance.
+ */
+ protected void setSupportsMultiInstance(boolean supportsMultiInstance) {
+ if (supportsMultiInstance) {
+ runtimeStatusFlags |= FLAG_SUPPORTS_MULTI_INSTANCE;
+ } else {
+ runtimeStatusFlags &= ~FLAG_SUPPORTS_MULTI_INSTANCE;
+ }
+ }
+
+ /**
+ * Returns whether this app info supports multi-instance.
+ */
+ public boolean supportsMultiInstance() {
+ return (runtimeStatusFlags & FLAG_SUPPORTS_MULTI_INSTANCE) != 0;
+ }
+
+ /**
+ * Sets whether this app info is non-resizeable.
+ */
+ public void setNonResizeable(boolean nonResizeable) {
+ if (nonResizeable) {
+ runtimeStatusFlags |= FLAG_NOT_RESIZEABLE;
+ } else {
+ runtimeStatusFlags &= ~FLAG_NOT_RESIZEABLE;
+ }
+ }
+
+ /**
+ * Returns whether this app info is resizeable.
+ */
+ public boolean isNonResizeable() {
+ return (runtimeStatusFlags & FLAG_NOT_RESIZEABLE) != 0;
+ }
+
/** Creates an intent to that launches the app store at this app's page. */
@Nullable
public Intent getMarketIntent(Context context) {
String targetPackage = getTargetPackage();
return targetPackage != null
- ? ApiWrapper.getAppMarketActivityIntent(
- context, targetPackage, Process.myUserHandle())
+ ? ApiWrapper.INSTANCE.get(context).getAppMarketActivityIntent(
+ targetPackage, Process.myUserHandle())
: null;
}
@@ -281,4 +324,11 @@
drawable.setIsDisabled(isDisabled());
return drawable;
}
+
+ @Override
+ protected String dumpProperties() {
+ return super.dumpProperties()
+ + " supportsMultiInstance=" + supportsMultiInstance()
+ + " nonResizeable=" + isNonResizeable();
+ }
}
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index 6fa8c54..f4dda55 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -271,8 +271,8 @@
@NonNull
@Override
- public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo folderInfo) {
- LauncherAtom.ItemInfo info = super.buildProto(folderInfo);
+ public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo collectionInfo) {
+ LauncherAtom.ItemInfo info = super.buildProto(collectionInfo);
return info.toBuilder()
.setWidget(info.getWidget().toBuilder().setWidgetFeatures(widgetFeatures))
.addItemAttributes(getAttribute(sourceContainer))
diff --git a/src/com/android/launcher3/model/data/PrivateSpaceInstallAppButtonInfo.java b/src/com/android/launcher3/model/data/PrivateSpaceInstallAppButtonInfo.java
new file mode 100644
index 0000000..1e7281d
--- /dev/null
+++ b/src/com/android/launcher3/model/data/PrivateSpaceInstallAppButtonInfo.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model.data;
+
+import com.android.launcher3.LauncherSettings;
+
+/**
+ * Represents the Private Space Install App button in AllAppsView.
+ */
+public class PrivateSpaceInstallAppButtonInfo extends AppInfo {
+
+ public PrivateSpaceInstallAppButtonInfo() {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON;
+ }
+}
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 9917ad7..40e3813 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.text.TextUtils;
+import android.util.Log;
import androidx.annotation.NonNull;
@@ -32,7 +33,7 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.ContentWriter;
import java.util.Arrays;
@@ -75,12 +76,6 @@
public static final int FLAG_START_FOR_RESULT = 1 << 4;
/**
- * The app is flagged non-resizeable, meaning that it does not support multi-window on small
- * screens.
- */
- public static final int FLAG_NON_RESIZEABLE = 1 << 5;
-
- /**
* The intent used to start the application.
*/
@NonNull
@@ -192,16 +187,19 @@
if (shortcutInfo.isEnabled()) {
runtimeStatusFlags &= ~FLAG_DISABLED_BY_PUBLISHER;
} else {
+ Log.w(TAG, "updateFromDeepShortcutInfo: Updated shortcut has been disabled. "
+ + " package=" + shortcutInfo.getPackage()
+ + " disabledReason=" + shortcutInfo.getDisabledReason());
runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER;
}
- disabledMessage = shortcutInfo.getDisabledMessage();
+
if (shortcutInfo.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
runtimeStatusFlags |= FLAG_DISABLED_VERSION_LOWER;
} else {
runtimeStatusFlags &= ~FLAG_DISABLED_VERSION_LOWER;
}
- Person[] persons = ApiWrapper.getPersons(shortcutInfo);
+ Person[] persons = ApiWrapper.INSTANCE.get(context).getPersons(shortcutInfo);
personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY
: Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);
}
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
index 4115b3d..e38824c 100644
--- a/src/com/android/launcher3/notification/NotificationKeyData.java
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
@@ -38,6 +39,11 @@
public final String[] personKeysFromNotification;
public int count;
+ @VisibleForTesting
+ public NotificationKeyData(String notificationKey) {
+ this(notificationKey, null, 1, new String[]{});
+ }
+
private NotificationKeyData(String notificationKey, String shortcutId, int count,
String[] personKeysFromNotification) {
this.notificationKey = notificationKey;
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 77effca..e44ea1d 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -153,7 +153,7 @@
mPaginationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaginationPaint.setStyle(Style.FILL);
- mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
+ mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.pageIndicatorDotColor));
mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
mCircleGap = DOT_GAP_FACTOR * mDotRadius;
setOutlineProvider(new MyOutlineProver());
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 2ec994e..e66f496 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -29,9 +29,9 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.SessionCommitReceiver;
-import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.util.IntArray;
@@ -40,6 +40,7 @@
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.SafeCloseable;
import java.util.ArrayList;
import java.util.HashMap;
@@ -51,7 +52,7 @@
* Utility class to tracking install sessions
*/
@SuppressWarnings("NewApi")
-public class InstallSessionHelper {
+public class InstallSessionHelper implements SafeCloseable {
@NonNull
private static final String LOG = "InstallSessionHelper";
@@ -88,6 +89,9 @@
mLauncherApps = context.getSystemService(LauncherApps.class);
}
+ @Override
+ public void close() { }
+
@WorkerThread
@NonNull
private IntSet getPromiseIconIds() {
@@ -167,7 +171,7 @@
synchronized (mSessionVerifiedMap) {
if (!mSessionVerifiedMap.containsKey(pkg)) {
boolean hasSystemFlag = DEBUG || mAppContext.getPackageName().equals(pkg)
- || new PackageManagerHelper(mAppContext)
+ || PackageManagerHelper.INSTANCE.get(mAppContext)
.getApplicationInfo(pkg, user, ApplicationInfo.FLAG_SYSTEM) != null;
mSessionVerifiedMap.put(pkg, hasSystemFlag);
}
@@ -215,11 +219,15 @@
&& SessionCommitReceiver.isEnabled(mAppContext, getUserHandle(sessionInfo))
&& verifySessionInfo(sessionInfo)
&& !promiseIconAddedForId(sessionInfo.getSessionId())) {
- FileLog.d(LOG, "Adding package name to install queue: "
- + sessionInfo.getAppPackageName());
+ // In case of unarchival, we do not want to add a workspace promise icon if one is
+ // not already present. For general app installations however, we do support it.
+ if (!Flags.enableSupportForArchiving() || !sessionInfo.isUnarchival()) {
+ FileLog.d(LOG, "Adding package name to install queue: "
+ + sessionInfo.getAppPackageName());
- ItemInstallQueue.INSTANCE.get(mAppContext)
- .queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
+ ItemInstallQueue.INSTANCE.get(mAppContext)
+ .queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
+ }
getPromiseIconIds().add(sessionInfo.getSessionId());
updatePromiseIconPrefs();
@@ -228,7 +236,7 @@
public boolean verifySessionInfo(@Nullable final PackageInstaller.SessionInfo sessionInfo) {
// For archived apps we always want to show promise icons and the checks below don't apply.
- if (Utilities.enableSupportForArchiving() && sessionInfo != null
+ if (Flags.enableSupportForArchiving() && sessionInfo != null
&& sessionInfo.isUnarchival()) {
return true;
}
@@ -237,7 +245,7 @@
&& sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
&& sessionInfo.getAppIcon() != null
&& !TextUtils.isEmpty(sessionInfo.getAppLabel())
- && !new PackageManagerHelper(mAppContext).isAppInstalled(
+ && !PackageManagerHelper.INSTANCE.get(mAppContext).isAppInstalled(
sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
}
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index eacbc11..24d58f3 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -31,6 +31,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.Flags;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.PackageUserKey;
@@ -80,7 +81,7 @@
helper.tryQueuePromiseAppIcon(sessionInfo);
- if (Utilities.enableSupportForArchiving() && sessionInfo != null
+ if (Flags.enableSupportForArchiving() && sessionInfo != null
&& sessionInfo.isUnarchival()) {
// For archived apps, icon could already be present on the workspace. To make sure
// the icon state is updated, we send a change event.
diff --git a/src/com/android/launcher3/pm/PackageInstallInfo.java b/src/com/android/launcher3/pm/PackageInstallInfo.java
index 1797c1f..23d3b61 100644
--- a/src/com/android/launcher3/pm/PackageInstallInfo.java
+++ b/src/com/android/launcher3/pm/PackageInstallInfo.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
public final class PackageInstallInfo {
+ private static final String TAG = "PackageInstallInfo";
public static final int STATUS_INSTALLED = 0;
public static final int STATUS_INSTALLING = 1;
@@ -61,7 +62,7 @@
@Override
public String toString() {
- return getClass().getSimpleName() + "(" + dumpProperties() + ")";
+ return TAG + "(" + dumpProperties() + ")";
}
private String dumpProperties() {
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 032de31..ed25186 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -17,7 +17,6 @@
package com.android.launcher3.pm;
import static com.android.launcher3.Utilities.ATLEAST_U;
-import static com.android.launcher3.uioverrides.ApiWrapper.queryAllUsers;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.Context;
@@ -25,6 +24,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArrayMap;
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
@@ -34,6 +34,7 @@
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.UserBadgeDrawable;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
@@ -81,6 +82,9 @@
@NonNull
private Map<UserHandle, UserIconInfo> mUserToSerialMap;
+ @NonNull
+ private Map<UserHandle, List<String>> mUserToPreInstallAppMap;
+
private UserCache(Context context) {
mContext = context;
mUserToSerialMap = Collections.emptyMap();
@@ -97,6 +101,7 @@
mUserChangeReceiver.register(mContext,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
+ Intent.ACTION_MANAGED_PROFILE_REMOVED,
ACTION_PROFILE_ADDED,
ACTION_PROFILE_REMOVED,
ACTION_PROFILE_UNLOCKED,
@@ -119,7 +124,21 @@
@WorkerThread
private void updateCache() {
- mUserToSerialMap = queryAllUsers(mContext);
+ mUserToSerialMap = ApiWrapper.INSTANCE.get(mContext).queryAllUsers();
+ mUserToPreInstallAppMap = fetchPreInstallApps();
+ }
+
+ @WorkerThread
+ private Map<UserHandle, List<String>> fetchPreInstallApps() {
+ Map<UserHandle, List<String>> userToPreInstallApp = new ArrayMap<>();
+ 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)
+ : new ArrayList<>();
+ userToPreInstallApp.put(userHandle, preInstallApp);
+ });
+ return userToPreInstallApp;
}
/**
@@ -172,6 +191,15 @@
}
/**
+ * Returns the pre-installed apps for a user.
+ */
+ @NonNull
+ public List<String> getPreInstallApps(UserHandle user) {
+ List<String> preInstallApp = mUserToPreInstallAppMap.get(user);
+ return preInstallApp == null ? new ArrayList<>() : preInstallApp;
+ }
+
+ /**
* Get a non-themed {@link UserBadgeDrawable} based on the provided {@link UserHandle}.
*/
@Nullable
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 688da49..0860ae5 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -33,22 +33,22 @@
import android.widget.Toast;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.views.ActivityContext;
import java.lang.ref.WeakReference;
-public class RemoteActionShortcut extends SystemShortcut<BaseDraggingActivity> {
+public class RemoteActionShortcut<T extends Context & ActivityContext> extends SystemShortcut<T> {
private static final String TAG = "RemoteActionShortcut";
private static final boolean DEBUG = Utilities.IS_DEBUG_DEVICE;
private final RemoteAction mAction;
public RemoteActionShortcut(RemoteAction action,
- BaseDraggingActivity activity, ItemInfo itemInfo, View originalView) {
- super(0, R.id.action_remote_action_shortcut, activity, itemInfo, originalView);
+ T context, ItemInfo itemInfo, View originalView) {
+ super(0, R.id.action_remote_action_shortcut, context, itemInfo, originalView);
mAction = action;
}
@@ -80,7 +80,7 @@
mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
.log(LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP);
- final WeakReference<BaseDraggingActivity> weakTarget = new WeakReference<>(mTarget);
+ final WeakReference<T> weakTarget = new WeakReference<>(mTarget);
final String actionIdentity = mAction.getTitle() + ", "
+ mItemInfo.getTargetComponent().getPackageName();
@@ -95,7 +95,7 @@
mItemInfo.getTargetComponent().getPackageName()),
(pendingIntent, intent, resultCode, resultData, resultExtras) -> {
if (DEBUG) Log.d(TAG, "Action is complete: " + actionIdentity);
- final BaseDraggingActivity target = weakTarget.get();
+ final T target = weakTarget.get();
if (resultData != null && !resultData.isEmpty()) {
Log.e(TAG, "Remote action returned result: " + actionIdentity
+ " : " + resultData);
diff --git a/src/com/android/launcher3/popup/RoundedArrowDrawable.java b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
index 436aa51..2610bd6 100644
--- a/src/com/android/launcher3/popup/RoundedArrowDrawable.java
+++ b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
@@ -84,11 +84,12 @@
* @param width of the arrow.
* @param height of the arrow.
* @param radius of the tip of the arrow.
- * @param isPointingLeft or not.
+ * @param isHorizontal or not.
+ * @param isLeftOrTop or not.
* @param color to draw the triangle.
*/
- public RoundedArrowDrawable(float width, float height, float radius, boolean isPointingLeft,
- int color) {
+ private RoundedArrowDrawable(float width, float height, float radius, boolean isHorizontal,
+ boolean isLeftOrTop, int color) {
mPath = new Path();
mPaint = new Paint();
mPaint.setColor(color);
@@ -98,10 +99,47 @@
// Make the drawable with the triangle pointing down...
addDownPointingRoundedTriangleToPath(width, height, radius, mPath);
- // ... then rotate it to the side it needs to point.
- Matrix pathTransform = new Matrix();
- pathTransform.setRotate(isPointingLeft ? 90 : -90, width * 0.5f, height * 0.5f);
- mPath.transform(pathTransform);
+ if (isHorizontal || isLeftOrTop) {
+ // ... then rotate it to the side it needs to point.
+ Matrix pathTransform = new Matrix();
+ int rotationAngle;
+ if (isHorizontal) {
+ rotationAngle = isLeftOrTop ? 90 : -90;
+ } else {
+ // it could only be vertical arrow pointing up
+ rotationAngle = 180;
+ }
+ pathTransform.setRotate(rotationAngle, width * 0.5f, height * 0.5f);
+ mPath.transform(pathTransform);
+ }
+ }
+
+ /**
+ * factory method for an arrow that points to the left or right.
+ *
+ * @param width of the arrow.
+ * @param height of the arrow.
+ * @param radius of the tip of the arrow.
+ * @param isPointingLeft or not.
+ * @param color to draw the triangle.
+ */
+ public static RoundedArrowDrawable createHorizontalRoundedArrow(float width, float height,
+ float radius, boolean isPointingLeft, int color) {
+ return new RoundedArrowDrawable(width, height, radius, true, isPointingLeft, color);
+ }
+
+ /**
+ * factory method for an arrow that points to the left or right.
+ *
+ * @param width of the arrow.
+ * @param height of the arrow.
+ * @param radius of the tip of the arrow.
+ * @param isPointingUp or not.
+ * @param color to draw the triangle.
+ */
+ public static RoundedArrowDrawable createVerticalRoundedArrow(float width, float height,
+ float radius, boolean isPointingUp, int color) {
+ return new RoundedArrowDrawable(width, height, radius, false, isPointingUp, color);
}
@Override
@@ -129,7 +167,18 @@
mPaint.setColorFilter(colorFilter);
}
- private static void addDownPointingRoundedTriangleToPath(float width, float height,
+ /**
+ * Set shadow layer to internal {@link Paint#setShadowLayer(float, float, float, int) paint}
+ * object
+ */
+ public void setShadowLayer(float shadowBlur, float dx, float dy, int shadowColor) {
+ mPaint.setShadowLayer(shadowBlur, dx, dy, shadowColor);
+ }
+
+ /**
+ * Adds rounded triangle pointing down to the provided {@link Path path} argument
+ */
+ public static void addDownPointingRoundedTriangleToPath(float width, float height,
float radius, Path path) {
// Calculated for the arrow pointing down, will be flipped later if needed.
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 0af7e67..83e9810 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -22,6 +22,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AbstractFloatingViewHelper;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.SecondaryDropTarget;
@@ -31,7 +32,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageManagerHelper;
@@ -61,23 +62,23 @@
protected final ItemInfo mItemInfo;
protected final View mOriginalView;
+ private final AbstractFloatingViewHelper mAbstractFloatingViewHelper;
+
public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo,
View originalView) {
+ this(iconResId, labelResId, target, itemInfo, originalView,
+ new AbstractFloatingViewHelper());
+ }
+
+ public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo,
+ View originalView, AbstractFloatingViewHelper abstractFloatingViewHelper) {
mIconResId = iconResId;
mLabelResId = labelResId;
mAccessibilityActionId = labelResId;
mTarget = target;
mItemInfo = itemInfo;
mOriginalView = originalView;
- }
-
- public SystemShortcut(SystemShortcut<T> other) {
- mIconResId = other.mIconResId;
- mLabelResId = other.mLabelResId;
- mAccessibilityActionId = other.mAccessibilityActionId;
- mTarget = other.mTarget;
- mItemInfo = other.mItemInfo;
- mOriginalView = other.mOriginalView;
+ mAbstractFloatingViewHelper = abstractFloatingViewHelper;
}
public void setIconAndLabelFor(View iconView, TextView labelView) {
@@ -178,10 +179,10 @@
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
Rect sourceBounds = Utilities.getViewBounds(view);
- new PackageManagerHelper(view.getContext()).startDetailsActivityForInfo(
- mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
+ PackageManagerHelper.startDetailsActivityForInfo(view.getContext(), mItemInfo,
+ sourceBounds, ActivityOptions.makeBasic().toBundle());
mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
.log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP);
}
@@ -259,10 +260,8 @@
@Override
public void onClick(View view) {
Intent intent =
- ApiWrapper.getAppMarketActivityIntent(
- view.getContext(),
- mItemInfo.getTargetComponent().getPackageName(),
- mSpaceUser);
+ ApiWrapper.INSTANCE.get(view.getContext()).getAppMarketActivityIntent(
+ mItemInfo.getTargetComponent().getPackageName(), mSpaceUser);
mTarget.startActivitySafely(view, intent, mItemInfo);
AbstractFloatingView.closeAllOpenViews(mTarget);
mTarget.getStatsLogManager()
@@ -303,9 +302,8 @@
@Override
public void onClick(View view) {
- Intent intent = ApiWrapper.getAppMarketActivityIntent(view.getContext(),
- mItemInfo.getTargetComponent().getPackageName(),
- Process.myUserHandle());
+ Intent intent = ApiWrapper.INSTANCE.get(view.getContext()).getAppMarketActivityIntent(
+ mItemInfo.getTargetComponent().getPackageName(), Process.myUserHandle());
mTarget.startActivitySafely(view, intent, mItemInfo);
AbstractFloatingView.closeAllOpenViews(mTarget);
}
@@ -327,7 +325,7 @@
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
mTarget.getStatsLogManager().logger()
.withItemInfo(mItemInfo)
.log(LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP);
@@ -362,7 +360,8 @@
UninstallApp(T target, ItemInfo itemInfo, @NonNull View originalView,
@NonNull ComponentName cn) {
- super(R.drawable.ic_uninstall_no_shadow, R.string.uninstall_drop_target_label, target,
+ super(R.drawable.ic_uninstall_no_shadow,
+ R.string.uninstall_private_system_shortcut_label, target,
itemInfo, originalView);
mComponentName = cn;
@@ -370,7 +369,7 @@
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
SecondaryDropTarget.performUninstall(view.getContext(), mComponentName, mItemInfo);
mTarget.getStatsLogManager()
.logger()
@@ -379,8 +378,8 @@
}
}
- public static <T extends ActivityContext> void dismissTaskMenuView(T activity) {
- AbstractFloatingView.closeOpenViews(activity, true,
+ protected void dismissTaskMenuView() {
+ mAbstractFloatingViewHelper.closeOpenViews(mTarget, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
}
}
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index 1f15947..3ae643e 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -44,6 +44,7 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.PackageManagerHelper;
/**
* A set of utility methods for Launcher DB used for DB updates and migration.
@@ -107,9 +108,11 @@
Cursor c = db.query(
Favorites.TABLE_NAME, null, "itemType = 1", null, null, null, null);
UserManagerState ums = new UserManagerState();
+ PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context);
ums.init(UserCache.INSTANCE.get(context),
context.getSystemService(UserManager.class));
- LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums, null);
+ LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums, pmHelper,
+ null);
IntSet deletedShortcuts = new IntSet();
while (lc.moveToNext()) {
@@ -152,7 +155,12 @@
}
ShortcutInfo info = infoBuilder.build();
- if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) {
+ try {
+ if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) {
+ deletedShortcuts.add(lc.id);
+ continue;
+ }
+ } catch (Exception e) {
deletedShortcuts.add(lc.id);
continue;
}
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 22bc13b..21897bf 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -18,6 +18,7 @@
import static android.os.Process.myUserHandle;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.Flags.enableLauncherBrMetricsFixed;
import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS;
@@ -50,8 +51,10 @@
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherFiles;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -62,20 +65,21 @@
import com.android.launcher3.model.DeviceGridState;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LogConfig;
+import java.io.File;
import java.io.InvalidObjectException;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -121,7 +125,66 @@
// executed again.
LauncherPrefs.get(context).removeSync(RESTORE_DEVICE);
- idp.reinitializeAfterRestore(context);
+ if (Flags.enableNarrowGridRestore()) {
+ String oldPhoneFileName = idp.dbFile;
+ List<String> previousDbs = existingDbs();
+ removeOldDBs(context, oldPhoneFileName);
+ // The idp before this contains data about the old phone, after this it becomes the idp
+ // of the current phone.
+ idp.reset(context);
+ trySettingPreviousGidAsCurrent(context, idp, oldPhoneFileName, previousDbs);
+ } else {
+ idp.reinitializeAfterRestore(context);
+ }
+ }
+
+
+ /**
+ * Try setting the gird used in the previous phone to the new one. If the current device doesn't
+ * support the previous grid option it will not be set.
+ */
+ private static void trySettingPreviousGidAsCurrent(Context context, InvariantDeviceProfile idp,
+ String oldPhoneDbFileName, List<String> previousDbs) {
+ InvariantDeviceProfile.GridOption oldPhoneGridOption = idp.getGridOptionFromFileName(
+ context, oldPhoneDbFileName);
+ // The grid option could be null if current phone doesn't support the previous db.
+ if (oldPhoneGridOption != null) {
+ /* If the user only used the default db on the previous phone and the new default db is
+ * bigger than or equal to the previous one, then keep the new default db */
+ if (previousDbs.size() == 1 && oldPhoneGridOption.numColumns <= idp.numColumns
+ && oldPhoneGridOption.numRows <= idp.numRows) {
+ /* Keep the user in default grid */
+ return;
+ }
+ /*
+ * Here we are setting the previous db as the current one.
+ */
+ idp.setCurrentGrid(context, oldPhoneGridOption.name);
+ }
+ }
+
+ /**
+ * Returns a list of paths of the existing launcher dbs.
+ */
+ private static List<String> existingDbs() {
+ // At this point idp.dbFile contains the name of the dbFile from the previous phone
+ return LauncherFiles.GRID_DB_FILES.stream()
+ .filter(dbName -> new File(dbName).exists())
+ .toList();
+ }
+
+ /**
+ * Only keep the last database used on the previous device.
+ */
+ private static void removeOldDBs(Context context, String oldPhoneDbFileName) {
+ // At this point idp.dbFile contains the name of the dbFile from the previous phone
+ LauncherFiles.GRID_DB_FILES.stream()
+ .filter(dbName -> !dbName.equals(oldPhoneDbFileName))
+ .forEach(dbName -> {
+ if (context.getDatabasePath(dbName).delete()) {
+ FileLog.d(TAG, "Removed old grid db file: " + dbName);
+ }
+ });
}
private static boolean performRestore(Context context, ModelDbController controller) {
@@ -355,9 +418,7 @@
DeviceGridState deviceGridState = new DeviceGridState(context);
FileLog.d(TAG, "restore initiated from backup: DeviceGridState=" + deviceGridState);
LauncherPrefs.get(context).putSync(RESTORE_DEVICE.to(deviceGridState.getDeviceType()));
- if (enableLauncherBrMetricsFixed()) {
- LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
- }
+ LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
}
@WorkerThread
@@ -385,7 +446,7 @@
private void restoreAppWidgetIds(Context context, ModelDbController controller,
LauncherRestoreEventLogger launcherRestoreEventLogger, int[] oldWidgetIds,
int[] newWidgetIds, @NonNull AppWidgetHost host) {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
FileLog.e(TAG, "Skipping widget ID remap as widgets not supported");
host.deleteHost();
launcherRestoreEventLogger.logFavoritesItemsRestoreFailed(Favorites.ITEM_TYPE_APPWIDGET,
@@ -413,7 +474,7 @@
logDatabaseWidgetInfo(controller);
for (int i = 0; i < oldWidgetIds.length; i++) {
- FileLog.i(TAG, "Widget state restore id " + oldWidgetIds[i] + " => " + newWidgetIds[i]);
+ FileLog.i(TAG, "migrating appWidgetId: " + oldWidgetIds[i] + " => " + newWidgetIds[i]);
final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]);
final int state;
@@ -463,10 +524,7 @@
}
logFavoritesTable(controller.getDb(), "launcher db after remap widget ids", null, null);
- LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app != null) {
- app.getModel().forceReload();
- }
+ LauncherAppState.INSTANCE.executeIfCreated(app -> app.getModel().forceReload());
}
private static void logDatabaseWidgetInfo(ModelDbController controller) {
@@ -514,9 +572,8 @@
protected static void maybeOverrideShortcuts(Context context, ModelDbController controller,
SQLiteDatabase db, long currentUser) {
- Map<String, LauncherActivityInfo> activityOverrides = ApiWrapper.getActivityOverrides(
- context);
-
+ Map<String, LauncherActivityInfo> activityOverrides =
+ ApiWrapper.INSTANCE.get(context).getActivityOverrides();
if (activityOverrides == null || activityOverrides.isEmpty()) {
return;
}
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index f0f376f..d6b41b0 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -20,7 +20,7 @@
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_PROVIDER;
-import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
+import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import android.app.Activity;
import android.app.Fragment;
@@ -61,7 +61,7 @@
*/
public class QsbContainerView extends FrameLayout {
- public static final String SEARCH_PROVIDER_SETTINGS_KEY = "SEARCH_PROVIDER_PACKAGE_NAME";
+ public static final String SEARCH_ENGINE_SETTINGS_KEY = "selected_search_engine";
/**
* Returns the package name for user configured search provider or from searchManager
@@ -71,8 +71,8 @@
@WorkerThread
@Nullable
public static String getSearchWidgetPackageName(@NonNull Context context) {
- String providerPkg = Settings.Global.getString(context.getContentResolver(),
- SEARCH_PROVIDER_SETTINGS_KEY);
+ String providerPkg = Settings.Secure.getString(context.getContentResolver(),
+ SEARCH_ENGINE_SETTINGS_KEY);
if (providerPkg == null) {
SearchManager searchManager = context.getSystemService(SearchManager.class);
ComponentName componentName = searchManager.getGlobalSearchActivity();
@@ -292,7 +292,8 @@
}
public boolean isQsbEnabled() {
- return FeatureFlags.QSB_ON_FIRST_SCREEN && !shouldShowFirstPageWidget();
+ return FeatureFlags.QSB_ON_FIRST_SCREEN
+ && !SHOULD_SHOW_FIRST_PAGE_WIDGET;
}
protected Bundle createBindOptions() {
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index 43027da..6d6b3b6 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -26,7 +26,9 @@
import com.android.launcher3.util.CancellableTask
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
+import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.views.ActivityContext.ActivityContextDelegate
const val PREINFLATE_ICONS_ROW_COUNT = 4
const val EXTRA_ICONS_COUNT = 2
@@ -52,12 +54,28 @@
return
}
+ // Create a separate context dedicated for all apps preinflation thread. The goal is to
+ // create a separate AssetManager obj internally to avoid lock contention with
+ // AssetManager obj that is associated with the launcher context on the main thread.
+ val allAppsPreInflationContext =
+ ActivityContextDelegate(
+ context.createConfigurationContext(context.resources.configuration),
+ Themes.getActivityThemeRes(context),
+ context
+ )
+
// Because we perform onCreateViewHolder() on worker thread, we need a separate
// adapter/inflator object as they are not thread-safe. Note that the adapter
// just need to perform onCreateViewHolder(parent, VIEW_TYPE_ICON) so it doesn't need
// data source information.
val adapter: RecyclerView.Adapter<BaseAllAppsAdapter.ViewHolder> =
- object : BaseAllAppsAdapter<T>(context, context.appsView.layoutInflater, null, null) {
+ object :
+ BaseAllAppsAdapter<T>(
+ context,
+ context.appsView.layoutInflater.cloneInContext(allAppsPreInflationContext),
+ null,
+ null
+ ) {
override fun setAppsPerRow(appsPerRow: Int) = Unit
override fun getLayoutManager(): RecyclerView.LayoutManager? = null
}
diff --git a/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
index 7502a43..2c3035f 100644
--- a/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
+++ b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
@@ -135,7 +135,7 @@
hotseatQsbSpace.fixedSize + edgePadding.fixedSize <= maxAvailableSize
private fun logError(message: String) {
- Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
+ Log.e(LOG_TAG, "$LOG_TAG #isValid - $message - $this")
}
companion object {
diff --git a/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt b/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt
index a4b25e5..ef9b7df 100644
--- a/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt
@@ -31,7 +31,7 @@
groupOfSpecs
.onEach { group ->
check(group.widthSpecs.isEmpty() && group.heightSpecs.isNotEmpty()) {
- "${this::class.simpleName} is invalid, only heightSpecs are allowed - " +
+ "$LOG_TAG is invalid, only heightSpecs are allowed - " +
"width list size = ${group.widthSpecs.size}; " +
"height list size = ${group.heightSpecs.size}."
}
@@ -65,6 +65,7 @@
}
companion object {
+ private const val LOG_TAG = "ResponsiveCellSpecsProvider"
@JvmStatic
fun create(resourceHelper: ResourceHelper): ResponsiveCellSpecsProvider {
val parser = ResponsiveSpecsParser(resourceHelper)
@@ -137,11 +138,11 @@
}
private fun logError(message: String) {
- Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
+ Log.e(LOG_TAG, "$LOG_TAG#isValid - $message - $this")
}
companion object {
- private const val LOG_TAG = "CellSpec"
+ const val LOG_TAG = "CellSpec"
}
}
@@ -182,6 +183,7 @@
)
companion object {
+ private const val LOG_TAG = "CalculatedCellSpec"
private fun getCalculatedValue(
availableSpace: Int,
spec: SizeSpec,
@@ -191,10 +193,10 @@
}
override fun toString(): String {
- return "${this::class.simpleName}(" +
+ return "$LOG_TAG(" +
"availableSpace=$availableSpace, iconSize=$iconSize, " +
"iconTextSize=$iconTextSize, iconDrawablePadding=$iconDrawablePadding, " +
- "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
+ "${CellSpec.LOG_TAG}.maxAvailableSize=${spec.maxAvailableSize}" +
")"
}
}
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpec.kt b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
index 65e0b32..e69324d 100644
--- a/src/com/android/launcher3/responsive/ResponsiveSpec.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
@@ -154,7 +154,7 @@
}
private fun logError(message: String) {
- Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
+ Log.e(LOG_TAG, "$LOG_TAG#isValid - $message - $this")
}
enum class DimensionType {
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt b/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
index 67eaac0..654608d 100644
--- a/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
@@ -40,7 +40,7 @@
groupOfSpecs
.onEach { group ->
check(group.widthSpecs.isNotEmpty() && group.heightSpecs.isNotEmpty()) {
- "${this::class.simpleName} is incomplete - " +
+ "$LOG_TAG is incomplete - " +
"width list size = ${group.widthSpecs.size}; " +
"height list size = ${group.heightSpecs.size}."
}
@@ -124,6 +124,7 @@
}
companion object {
+ private const val LOG_TAG = "ResponsiveSpecsProvider"
@JvmStatic
fun create(
resourceHelper: ResourceHelper,
diff --git a/src/com/android/launcher3/search/StringMatcherUtility.java b/src/com/android/launcher3/search/StringMatcherUtility.java
index 28fc4f0..7446314 100644
--- a/src/com/android/launcher3/search/StringMatcherUtility.java
+++ b/src/com/android/launcher3/search/StringMatcherUtility.java
@@ -18,6 +18,8 @@
import android.text.TextUtils;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.util.IntArray;
import java.text.Collator;
@@ -120,7 +122,11 @@
/**
* Returns true if {@param query} is a prefix of {@param target}
*/
- public boolean matches(String query, String target) {
+ public boolean matches(@Nullable String query, @Nullable String target) {
+ // `mCollator.compare` requires non-null inputs, so return false earlier (not a match)
+ if (query == null || target == null) {
+ return false;
+ }
switch (mCollator.compare(query, target)) {
case 0:
return true;
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 910b029..0299a23 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -200,11 +200,6 @@
}
@Override
- public <T extends View> T getOverviewPanel() {
- return null;
- }
-
- @Override
public View getRootView() {
return mDragLayer;
}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index a5f9c2a..52ce4e8 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -41,7 +41,6 @@
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback;
@@ -52,11 +51,8 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.LauncherFiles;
import com.android.launcher3.R;
-import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.uioverrides.flags.DeveloperOptionsUI;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.Executors;
import com.android.launcher3.util.SettingsCache;
/**
@@ -238,7 +234,7 @@
protected boolean initPreference(Preference preference) {
switch (preference.getKey()) {
case NOTIFICATION_DOTS_PREFERENCE_KEY:
- return !WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS;
+ return BuildConfig.NOTIFICATION_DOTS_ENABLED;
case ALLOW_ROTATION_PREFERENCE_KEY:
DisplayController.Info info =
@@ -256,12 +252,6 @@
preference.setOrder(0);
}
return mDeveloperOptionsEnabled;
- case "pref_developer_flags":
- if (mDeveloperOptionsEnabled && preference instanceof PreferenceCategory pc) {
- Executors.MAIN_EXECUTOR.post(() -> new DeveloperOptionsUI(this, pc));
- return true;
- }
- return false;
}
return true;
diff --git a/src/com/android/launcher3/shortcuts/ShortcutRequest.java b/src/com/android/launcher3/shortcuts/ShortcutRequest.java
index 21efceb..015d54d 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutRequest.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutRequest.java
@@ -16,7 +16,7 @@
package com.android.launcher3.shortcuts;
-import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import android.content.ComponentName;
import android.content.Context;
@@ -47,7 +47,7 @@
| ShortcutQuery.FLAG_MATCH_MANIFEST;
public static final int PINNED = ShortcutQuery.FLAG_MATCH_PINNED;
- private final ShortcutQuery mQuery = GO_DISABLE_WIDGETS ? null : new ShortcutQuery();
+ private final ShortcutQuery mQuery = !WIDGETS_ENABLED ? null : new ShortcutQuery();
private final Context mContext;
private final UserHandle mUserHandle;
@@ -74,7 +74,7 @@
* @return A list of ShortcutInfo's associated with the given package.
*/
public ShortcutRequest forPackage(String packageName, @Nullable List<String> shortcutIds) {
- if (!GO_DISABLE_WIDGETS && packageName != null) {
+ if (WIDGETS_ENABLED && packageName != null) {
mQuery.setPackage(packageName);
mQuery.setShortcutIds(shortcutIds);
}
@@ -82,7 +82,7 @@
}
public ShortcutRequest withContainer(@Nullable ComponentName activity) {
- if (!GO_DISABLE_WIDGETS) {
+ if (WIDGETS_ENABLED) {
if (activity == null) {
mFailed = true;
} else {
@@ -93,7 +93,7 @@
}
public QueryResult query(int flags) {
- if (GO_DISABLE_WIDGETS || mFailed) {
+ if (!WIDGETS_ENABLED || mFailed) {
return QueryResult.DEFAULT;
}
mQuery.setQueryFlags(flags);
@@ -109,7 +109,7 @@
public static class QueryResult extends ArrayList<ShortcutInfo> {
- static final QueryResult DEFAULT = new QueryResult(GO_DISABLE_WIDGETS);
+ static final QueryResult DEFAULT = new QueryResult(!WIDGETS_ENABLED);
private final boolean mWasSuccess;
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index a01d402..b81729a 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -21,7 +21,7 @@
import com.android.launcher3.views.ActivityContext;
/**
- * Interface representing a state of a StatefulActivity
+ * Interface representing a state of a StatefulContainer
*/
public interface BaseState<T extends BaseState> {
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 51bc339..ac07c0f 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -26,6 +26,7 @@
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;
@@ -47,16 +48,19 @@
/**
* 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
*/
-public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>> {
+public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>,
+ STATEFUL_CONTAINER extends Context & StatefulContainer<STATE_TYPE>> {
public static final String TAG = "StateManager";
- // b/279059025
+ // b/279059025, b/325463989
private static final boolean DEBUG = true;
private final AnimationState mConfig = new AnimationState();
private final Handler mUiHandler;
- private final StatefulActivity<STATE_TYPE> mActivity;
+ private final STATEFUL_CONTAINER mStatefulContainer;
private final ArrayList<StateListener<STATE_TYPE>> mListeners = new ArrayList<>();
private final STATE_TYPE mBaseState;
@@ -71,12 +75,12 @@
private STATE_TYPE mRestState;
- public StateManager(StatefulActivity<STATE_TYPE> l, STATE_TYPE baseState) {
+ public StateManager(STATEFUL_CONTAINER container, STATE_TYPE baseState) {
mUiHandler = new Handler(Looper.getMainLooper());
- mActivity = l;
+ mStatefulContainer = container;
mBaseState = baseState;
mState = mLastStableState = mCurrentStableState = baseState;
- mAtomicAnimationFactory = l.createAtomicAnimationFactory();
+ mAtomicAnimationFactory = container.createAtomicAnimationFactory();
}
public STATE_TYPE getState() {
@@ -109,10 +113,10 @@
writer.println(prefix + "\tisInTransition:" + isInTransition());
}
- public StateHandler[] getStateHandlers() {
+ public StateHandler<STATE_TYPE>[] getStateHandlers() {
if (mStateHandlers == null) {
- ArrayList<StateHandler> handlers = new ArrayList<>();
- mActivity.collectStateHandlers(handlers);
+ ArrayList<StateHandler<STATE_TYPE>> handlers = new ArrayList<>();
+ mStatefulContainer.collectStateHandlers(handlers);
mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]);
}
return mStateHandlers;
@@ -130,7 +134,7 @@
* Returns true if the state changes should be animated.
*/
public boolean shouldAnimateStateChange() {
- return !mActivity.isForceInvisible() && mActivity.isStarted();
+ return mStatefulContainer.shouldAnimateStateChange();
}
/**
@@ -240,20 +244,12 @@
private void goToState(
STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
if (DEBUG) {
- String stackTrace = Log.getStackTraceString(new Exception("tracing state transition"));
- String truncatedTrace =
- Arrays.stream(stackTrace.split("\\n"))
- .limit(5)
- .skip(1) // Removes the line "java.lang.Exception: tracing state
- // transition"
- .filter(traceLine -> !traceLine.contains("StateManager.goToState"))
- .collect(Collectors.joining("\n"));
Log.d(TAG, "goToState - fromState: " + mState + ", toState: " + state
- + ", partial trace:\n" + truncatedTrace);
+ + ", partial trace:\n" + getTrimmedStackTrace("StateManager.goToState"));
}
animated &= areAnimatorsEnabled();
- if (mActivity.isInState(state)) {
+ if (mStatefulContainer.isInState(state)) {
if (mConfig.currentAnimation == null) {
// Run any queued runnable
if (listener != null) {
@@ -310,8 +306,8 @@
// 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(mActivity, false /* isToState */)
- : state.getTransitionDuration(mActivity, true /* isToState */);
+ ? fromState.getTransitionDuration(mStatefulContainer, false /* isToState */)
+ : state.getTransitionDuration(mStatefulContainer, true /* isToState */);
prepareForAtomicAnimation(fromState, state, mConfig);
AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
if (listener != null) {
@@ -336,23 +332,15 @@
public AnimatorSet createAtomicAnimation(
STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) {
if (DEBUG) {
- String stackTrace = Log.getStackTraceString(new Exception("tracing state transition"));
- String truncatedTrace =
- Arrays.stream(stackTrace.split("\\n"))
- .limit(5)
- .skip(1) // Removes the line "java.lang.Exception: tracing state
- // transition"
- .filter(traceLine -> !traceLine.contains(
- "StateManager.createAtomicAnimation"))
- .collect(Collectors.joining("\n"));
Log.d(TAG, "createAtomicAnimation - fromState: " + fromState + ", toState: " + toState
- + ", partial trace:\n" + truncatedTrace);
+ + ", partial trace:\n" + getTrimmedStackTrace(
+ "StateManager.createAtomicAnimation"));
}
PendingAnimation builder = new PendingAnimation(config.duration);
prepareForAtomicAnimation(fromState, toState, config);
- for (StateHandler handler : mActivity.getStateManager().getStateHandlers()) {
+ for (StateHandler handler : mStatefulContainer.getStateManager().getStateHandlers()) {
handler.setStateWithAnimation(toState, config, builder);
}
return builder.buildAnim();
@@ -418,7 +406,7 @@
private void onStateTransitionStart(STATE_TYPE state) {
mState = state;
- mActivity.onStateSetStart(mState);
+ mStatefulContainer.onStateSetStart(mState);
if (DEBUG) {
Log.d(TAG, "onStateTransitionStart - state: " + state);
@@ -435,7 +423,7 @@
mCurrentStableState = state;
}
- mActivity.onStateSetEnd(state);
+ mStatefulContainer.onStateSetEnd(state);
if (state == mBaseState) {
setRestState(null);
}
@@ -481,7 +469,8 @@
*/
public void cancelAnimation() {
if (DEBUG && mConfig.currentAnimation != null) {
- Log.d(TAG, "cancelAnimation - with ongoing animation");
+ Log.d(TAG, "cancelAnimation - with ongoing animation"
+ + ", partial trace:\n" + getTrimmedStackTrace("StateManager.cancelAnimation"));
}
mConfig.reset();
// It could happen that a new animation is set as a result of an endListener on the
@@ -579,6 +568,15 @@
mConfig.playbackController = null;
}
+ private String getTrimmedStackTrace(String callingMethodName) {
+ String stackTrace = Log.getStackTraceString(new Exception());
+ return Arrays.stream(stackTrace.split("\\n"))
+ .skip(2) // Removes the line "java.lang.Exception" and "getTrimmedStackTrace".
+ .filter(traceLine -> !traceLine.contains(callingMethodName))
+ .limit(3)
+ .collect(Collectors.joining("\n"));
+ }
+
private class StartAnimRunnable implements Runnable {
private final AnimatorSet mAnim;
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 30ba703..28f2def 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,7 +18,6 @@
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
import android.content.res.Configuration;
@@ -29,11 +28,9 @@
import androidx.annotation.CallSuper;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherRootView;
import com.android.launcher3.Utilities;
-import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.BaseDragLayer;
@@ -45,7 +42,7 @@
* @param <STATE_TYPE> Type of state object
*/
public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>>
- extends BaseDraggingActivity {
+ extends BaseDraggingActivity implements StatefulContainer<STATE_TYPE> {
public final Handler mHandler = new Handler();
private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -67,19 +64,9 @@
/**
* Create handlers to control the property changes for this activity
*/
- protected abstract void collectStateHandlers(List<StateHandler> out);
- /**
- * Returns true if the activity is in the provided state
- */
- public boolean isInState(STATE_TYPE state) {
- return getStateManager().getState() == state;
- }
-
- /**
- * Returns the state manager for this activity
- */
- public abstract StateManager<STATE_TYPE> getStateManager();
+ @Override
+ public abstract void collectStateHandlers(List<StateHandler<STATE_TYPE>> out);
protected void inflateRootView(int layoutId) {
mRootView = (LauncherRootView) LayoutInflater.from(this).inflate(layoutId, null);
@@ -106,22 +93,12 @@
if (mDeferredResumePending) {
handleDeferredResume();
}
-
- if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
- AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
- }
+ StatefulContainer.super.onStateSetStart(state);
}
- /**
- * Called when transition to state ends
- */
- public void onStateSetEnd(STATE_TYPE state) { }
-
- /**
- * Creates a factory for atomic state animations
- */
- public AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
- return new AtomicAnimationFactory(0);
+ @Override
+ public boolean shouldAnimateStateChange() {
+ return !isForceInvisible() && isStarted();
}
@Override
diff --git a/src/com/android/launcher3/statemanager/StatefulContainer.java b/src/com/android/launcher3/statemanager/StatefulContainer.java
new file mode 100644
index 0000000..0cf0a27
--- /dev/null
+++ b/src/com/android/launcher3/statemanager/StatefulContainer.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.statemanager;
+
+
+import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
+import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
+
+import androidx.annotation.CallSuper;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.List;
+
+/**
+ * Interface for a container that can be managed by a state manager.
+ *
+ * @param <STATE_TYPE> The type of state that the container can be in.
+ */
+public interface StatefulContainer<STATE_TYPE extends BaseState<STATE_TYPE>> extends
+ ActivityContext {
+
+ /**
+ * Creates a factory for atomic state animations
+ */
+ default StateManager.AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
+ return new StateManager.AtomicAnimationFactory<>(0);
+ }
+
+ /**
+ * Create handlers to control the property changes for this activity
+ */
+ void collectStateHandlers(List<StateManager.StateHandler<STATE_TYPE>> out);
+
+ /**
+ * Retrieves state manager for given container
+ */
+ StateManager<STATE_TYPE, ?> getStateManager();
+
+ /**
+ * Called when transition to state ends
+ * @param state current state of State_Type
+ */
+ default void onStateSetEnd(STATE_TYPE state) { }
+
+ /**
+ * Called when transition to state starts
+ * @param state current state of State_Type
+ */
+ @CallSuper
+ default void onStateSetStart(STATE_TYPE state) {
+ if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
+ AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
+ }
+ }
+
+ /**
+ * Returns true if the activity is in the provided state
+ * @param state current state of State_Type
+ */
+ default boolean isInState(STATE_TYPE state) {
+ return getStateManager().getState() == state;
+ }
+
+ /**
+ * Returns true if state change should transition with animation
+ */
+ boolean shouldAnimateStateChange();
+}
diff --git a/src/com/android/launcher3/states/EditModeState.kt b/src/com/android/launcher3/states/EditModeState.kt
index aafaaa0..6ff47ae 100644
--- a/src/com/android/launcher3/states/EditModeState.kt
+++ b/src/com/android/launcher3/states/EditModeState.kt
@@ -16,6 +16,7 @@
package com.android.launcher3.states
import android.content.Context
+import com.android.launcher3.Flags.enableScalingRevealHomeAnimation
import com.android.launcher3.Launcher
import com.android.launcher3.LauncherState
import com.android.launcher3.logging.StatsLogManager
@@ -25,6 +26,8 @@
class EditModeState(id: Int) : LauncherState(id, StatsLogManager.LAUNCHER_STATE_HOME, STATE_FLAGS) {
companion object {
+ const val DEPTH_15_PERCENT = 0.15f
+
private val STATE_FLAGS =
(FLAG_MULTI_PAGE or
FLAG_WORKSPACE_INACCESSIBLE or
@@ -40,7 +43,11 @@
}
override fun <T> getDepthUnchecked(context: T): Float where T : Context?, T : ActivityContext? {
- return 0.5f
+ if (enableScalingRevealHomeAnimation()) {
+ return DEPTH_15_PERCENT
+ } else {
+ return 0.5f
+ }
}
override fun getWorkspaceScaleAndTranslation(launcher: Launcher): ScaleAndTranslation {
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index 4cfced8..bf2fb30 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.states;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import android.content.Context;
@@ -34,6 +35,8 @@
private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE
| FLAG_HAS_SYS_UI_SCRIM;
+ public static final float DEPTH_5_PERCENT = 0.05f;
+
public HintState(int id) {
this(id, LAUNCHER_STATE_HOME);
}
@@ -49,7 +52,11 @@
@Override
protected float getDepthUnchecked(Context context) {
- return 0.15f;
+ if (enableScalingRevealHomeAnimation()) {
+ return DEPTH_5_PERCENT;
+ } else {
+ return 0.15f;
+ }
}
@Override
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 6950fb5..fdb37f0 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -35,6 +35,7 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.util.DisplayController;
@@ -42,6 +43,7 @@
* Utility class to manage launcher rotation
*/
public class RotationHelper implements OnSharedPreferenceChangeListener,
+ DeviceProfile.OnDeviceProfileChangeListener,
DisplayController.DisplayInfoChangeListener {
public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
@@ -119,10 +121,24 @@
}
}
+ /**
+ * Listening to both onDisplayInfoChanged and onDeviceProfileChanged to reduce delay. While
+ * onDeviceProfileChanged is triggered earlier, it only receives callback when Launcher is in
+ * the foreground. When in the background, we can still rely on onDisplayInfoChanged to update,
+ * assuming that the delay is tolerable since it takes time to change to foreground.
+ */
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+ onIgnoreAutoRotateChanged(info.isTablet(info.realBounds));
+ }
+
+ @Override
+ public void onDeviceProfileChanged(DeviceProfile dp) {
+ onIgnoreAutoRotateChanged(dp.isTablet);
+ }
+
+ private void onIgnoreAutoRotateChanged(boolean ignoreAutoRotateSettings) {
if (mDestroyed) return;
- boolean ignoreAutoRotateSettings = info.isTablet(info.realBounds);
if (mIgnoreAutoRotateSettings != ignoreAutoRotateSettings) {
setIgnoreAutoRotateSettings(ignoreAutoRotateSettings);
notifyChange();
@@ -161,12 +177,14 @@
DisplayController.Info info = displayController.getInfo();
setIgnoreAutoRotateSettings(info.isTablet(info.realBounds));
displayController.addChangeListener(this);
+ mActivity.addOnDeviceProfileChangeListener(this);
notifyChange();
}
public void destroy() {
if (mDestroyed) return;
mDestroyed = true;
+ mActivity.removeOnDeviceProfileChangeListener(this);
DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 3286afb..2e57ed8 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.states;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import android.content.Context;
@@ -33,6 +34,8 @@
| FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE
| FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_WORKSPACE_HAS_BACKGROUNDS;
+ public static final float DEPTH_15_PERCENT = 0.15f;
+
public SpringLoadedState(int id) {
super(id, LAUNCHER_STATE_HOME, STATE_FLAGS);
}
@@ -62,7 +65,11 @@
@Override
protected float getDepthUnchecked(Context context) {
- return 0.5f;
+ if (enableScalingRevealHomeAnimation()) {
+ return DEPTH_15_PERCENT;
+ } else {
+ return 0.5f;
+ }
}
@Override
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 1231cd7..db2a6e0 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -19,42 +19,60 @@
import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
+import static com.android.launcher3.config.FeatureFlags.enableAppPairs;
import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.app.Activity;
+import android.app.Application;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Bundle;
+import android.system.Os;
import android.view.WindowInsets;
+import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import androidx.core.view.WindowInsetsCompat;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
+import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Workspace;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.icons.ClockDrawableWrapper;
import com.android.launcher3.testing.shared.HotseatCellCenterRequest;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.testing.shared.WorkspaceCellCenterRequest;
+import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -68,15 +86,29 @@
context, R.string.test_information_handler_class);
}
+ private static Collection<String> sEvents;
+ private static Application.ActivityLifecycleCallbacks sActivityLifecycleCallbacks;
+ private static final Set<Activity> sActivities =
+ Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
+ private static int sActivitiesCreatedCount = 0;
+
protected Context mContext;
protected DeviceProfile mDeviceProfile;
- protected LauncherAppState mLauncherAppState;
public void init(Context context) {
mContext = context;
- mDeviceProfile = InvariantDeviceProfile.INSTANCE.
- get(context).getDeviceProfile(context);
- mLauncherAppState = LauncherAppState.getInstanceNoCreate();
+ mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context);
+ if (sActivityLifecycleCallbacks == null) {
+ sActivityLifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ sActivities.add(activity);
+ ++sActivitiesCreatedCount;
+ }
+ };
+ ((Application) context.getApplicationContext())
+ .registerActivityLifecycleCallbacks(sActivityLifecycleCallbacks);
+ }
}
/**
@@ -182,7 +214,10 @@
case TestProtocol.REQUEST_IS_TABLET:
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, mDeviceProfile.isTablet);
return response;
-
+ case TestProtocol.REQUEST_IS_PREDICTIVE_BACK_SWIPE_ENABLED:
+ response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ mDeviceProfile.isPredictiveBackSwipe);
+ return response;
case TestProtocol.REQUEST_ENABLE_TASKBAR_NAVBAR_UNIFICATION:
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
ENABLE_TASKBAR_NAVBAR_UNIFICATION);
@@ -300,6 +335,132 @@
return response;
}
+ case TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS: {
+ response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, enableAppPairs());
+ return response;
+ }
+
+ case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
+ return getLauncherUIProperty(Bundle::putInt,
+ l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
+ }
+
+ case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
+ TestProtocol.sDebugTracing = true;
+ ClockDrawableWrapper.sRunningInTest = true;
+ return response;
+
+ case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
+ TestProtocol.sDebugTracing = false;
+ ClockDrawableWrapper.sRunningInTest = false;
+ return response;
+
+ case TestProtocol.REQUEST_PID: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
+ return response;
+ }
+
+ case TestProtocol.REQUEST_FORCE_GC: {
+ runGcAndFinalizersSync();
+ return response;
+ }
+
+ case TestProtocol.REQUEST_START_EVENT_LOGGING: {
+ sEvents = new ArrayList<>();
+ TestLogging.setEventConsumer(
+ (sequence, event) -> {
+ final Collection<String> events = sEvents;
+ if (events != null) {
+ synchronized (events) {
+ events.add(sequence + '/' + event);
+ }
+ }
+ });
+ return response;
+ }
+
+ case TestProtocol.REQUEST_STOP_EVENT_LOGGING: {
+ TestLogging.setEventConsumer(null);
+ sEvents = null;
+ return response;
+ }
+
+ case TestProtocol.REQUEST_GET_TEST_EVENTS: {
+ if (sEvents == null) {
+ // sEvents can be null if Launcher died and restarted after
+ // REQUEST_START_EVENT_LOGGING.
+ return response;
+ }
+
+ synchronized (sEvents) {
+ response.putStringArrayList(
+ TestProtocol.TEST_INFO_RESPONSE_FIELD, new ArrayList<>(sEvents));
+ }
+ return response;
+ }
+
+ case TestProtocol.REQUEST_REINITIALIZE_DATA: {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ MODEL_EXECUTOR.execute(() -> {
+ LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
+ model.getModelDbController().createEmptyDB();
+ MAIN_EXECUTOR.execute(model::forceReload);
+ });
+ return response;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ case TestProtocol.REQUEST_CLEAR_DATA: {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ MODEL_EXECUTOR.execute(() -> {
+ LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
+ model.getModelDbController().createEmptyDB();
+ model.getModelDbController().clearEmptyDbFlag();
+ MAIN_EXECUTOR.execute(model::forceReload);
+ });
+ return response;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ case TestProtocol.REQUEST_HOTSEAT_ICON_NAMES: {
+ return getLauncherUIProperty(Bundle::putStringArrayList, l -> {
+ ShortcutAndWidgetContainer hotseatIconsContainer =
+ l.getHotseat().getShortcutsAndWidgets();
+ ArrayList<String> hotseatIconNames = new ArrayList<>();
+
+ for (int i = 0; i < hotseatIconsContainer.getChildCount(); i++) {
+ // Use unchecked cast to catch changes in hotseat layout
+ BubbleTextView icon = (BubbleTextView) hotseatIconsContainer.getChildAt(i);
+ hotseatIconNames.add((String) icon.getText());
+ }
+
+ return hotseatIconNames;
+ });
+ }
+
+ case TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, sActivitiesCreatedCount);
+ return response;
+ }
+
+ case TestProtocol.REQUEST_GET_ACTIVITIES: {
+ response.putStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ sActivities.stream().map(
+ a -> a.getClass().getSimpleName() + " ("
+ + (a.isDestroyed() ? "destroyed" : "current") + ")")
+ .toArray(String[]::new));
+ return response;
+ }
+
+ case TestProtocol.REQUEST_MODEL_QUEUE_CLEARED:
+ return getFromExecutorSync(MODEL_EXECUTOR, Bundle::new);
+
default:
return null;
}
@@ -378,4 +539,38 @@
*/
void set(Bundle b, String key, T value);
}
+
+
+ private static void runGcAndFinalizersSync() {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+
+ final CountDownLatch fence = new CountDownLatch(1);
+ createFinalizationObserver(fence);
+ try {
+ do {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ } while (!fence.await(100, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ // Create the observer in the scope of a method to minimize the chance that
+ // it remains live in a DEX/machine register at the point of the fence guard.
+ // This must be kept to avoid R8 inlining it.
+ @Keep
+ private static void createFinalizationObserver(CountDownLatch fence) {
+ new Object() {
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ fence.countDown();
+ } finally {
+ super.finalize();
+ }
+ }
+ };
+ }
}
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index 60d0e95..459fa07 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -62,7 +62,14 @@
public static void recordKeyEvent(String sequence, String message, KeyEvent event) {
if (Utilities.isRunningInTestHarness()) {
- recordEventSlow(sequence, message + ": " + event, true);
+ // This removes expecting ACTION_DOWN key event in the test. ACTION_UP is
+ // always preceded by ACTION_DOWN.
+ // Sometimes test doesn't receive ACTION_DOWN key event and we will assume that
+ // Launcher relies only on ACTION_UP.
+ // However in the test we will send both ACTION_DOWN and ACTION_UP key events.
+ // But stop reporting to tapl if action is down.
+ boolean reportToTapl = event.getAction() != KeyEvent.ACTION_DOWN;
+ recordEventSlow(sequence, message + ": " + event, reportToTapl);
registerEventNotFromTest(event);
}
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 9aed4eb..3817563 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -57,7 +57,7 @@
protected final SingleAxisSwipeDetector.Direction mSwipeDirection;
protected final AnimatorListener mClearStateOnCancelListener =
- newCancelListener(this::clearState);
+ newCancelListener(this::clearState, /* isSingleUse = */ false);
private final FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
protected int mStartContainerType;
@@ -411,17 +411,29 @@
mLauncher.getStatsLogManager().logger()
.withSrcState(mStartState.statsLogOrdinal)
.withDstState(targetState.statsLogOrdinal)
- .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
- .setWorkspace(
- LauncherAtom.WorkspaceContainer.newBuilder()
- .setPageIndex(mLauncher.getWorkspace().getCurrentPage()))
- .build())
+ .withContainerInfo(getContainerInfo(targetState))
.log(StatsLogManager.getLauncherAtomEvent(mStartState.statsLogOrdinal,
targetState.statsLogOrdinal, mToState.ordinal > mFromState.ordinal
? LAUNCHER_UNKNOWN_SWIPEUP
: LAUNCHER_UNKNOWN_SWIPEDOWN));
}
+ private LauncherAtom.ContainerInfo getContainerInfo(LauncherState targetState) {
+ if (targetState.isRecentsViewVisible) {
+ return LauncherAtom.ContainerInfo.newBuilder()
+ .setTaskSwitcherContainer(
+ LauncherAtom.TaskSwitcherContainer.getDefaultInstance()
+ )
+ .build();
+ }
+
+ return LauncherAtom.ContainerInfo.newBuilder()
+ .setWorkspace(
+ LauncherAtom.WorkspaceContainer.newBuilder()
+ .setPageIndex(mLauncher.getWorkspace().getCurrentPage()))
+ .build();
+ }
+
protected void clearState() {
cancelAnimationControllers();
mGoingBetweenStates = true;
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 111931e..f46dcd3 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -41,6 +41,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.BuildConfig;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
@@ -53,6 +54,7 @@
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo;
+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.ItemInfoWithIcon;
@@ -62,7 +64,7 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.Snackbar;
@@ -82,7 +84,8 @@
*/
public class ItemClickHandler {
- private static final String TAG = ItemClickHandler.class.getSimpleName();
+ private static final String TAG = "ItemClickHandler";
+ private static final boolean DEBUG = true;
/**
* Instance used for click handling on items
@@ -101,16 +104,26 @@
if (tag instanceof WorkspaceItemInfo) {
onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);
} else if (tag instanceof FolderInfo) {
- if (v instanceof FolderIcon) {
- onClickFolderIcon(v);
- } else if (v instanceof AppPairIcon) {
- onClickAppPairIcon(v);
- }
+ onClickFolderIcon(v);
+ } else if (tag instanceof AppPairInfo) {
+ onClickAppPairIcon(v);
} else if (tag instanceof AppInfo) {
startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
} else if (tag instanceof LauncherAppWidgetInfo) {
if (v instanceof PendingAppWidgetHostView) {
+ if (DEBUG) {
+ String targetPackage = ((LauncherAppWidgetInfo) tag).getTargetPackage();
+ Log.d(TAG, "onClick: PendingAppWidgetHostView clicked for"
+ + " package=" + targetPackage);
+ }
onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
+ } else {
+ if (DEBUG) {
+ String targetPackage = ((LauncherAppWidgetInfo) tag).getTargetPackage();
+ Log.d(TAG, "onClick: LauncherAppWidgetInfo clicked,"
+ + " but not instance of PendingAppWidgetHostView. Returning."
+ + " package=" + targetPackage);
+ }
}
} else if (tag instanceof ItemClickProxy) {
((ItemClickProxy) tag).onItemClicked(v);
@@ -120,6 +133,10 @@
launcher.getString(R.string.long_accessible_way_to_add_shortcut));
Snackbar.show(launcher, msg, null);
} else if (tag instanceof PendingAddWidgetInfo) {
+ if (DEBUG) {
+ String targetPackage = ((PendingAddWidgetInfo) tag).getTargetPackage();
+ Log.d(TAG, "onClick: PendingAddWidgetInfo clicked for package=" + targetPackage);
+ }
CharSequence msg = Utilities.wrapForTts(
launcher.getText(R.string.long_press_widget_to_add),
launcher.getString(R.string.long_accessible_way_to_add));
@@ -149,29 +166,40 @@
*/
private static void onClickAppPairIcon(View v) {
Launcher launcher = Launcher.getLauncher(v.getContext());
- AppPairIcon appPairIcon = (AppPairIcon) v;
- if (!appPairIcon.isLaunchableAtScreenSize()) {
- // Display a message for app pairs that are disabled due to screen size
+ AppPairIcon icon = (AppPairIcon) v;
+ AppPairInfo info = icon.getInfo();
+ boolean isApp1Launchable = info.isLaunchable(launcher).getFirst(),
+ isApp2Launchable = info.isLaunchable(launcher).getSecond();
+ if (!isApp1Launchable || !isApp2Launchable) {
+ // App pair is unlaunchable due to screen size.
boolean isFoldable = InvariantDeviceProfile.INSTANCE.get(launcher)
.supportedProfiles.stream().anyMatch(dp -> dp.isTwoPanels);
Toast.makeText(launcher, isFoldable
? R.string.app_pair_needs_unfold
: R.string.app_pair_unlaunchable_at_screen_size,
Toast.LENGTH_SHORT).show();
- } else if (appPairIcon.getInfo().isDisabled()) {
- WorkspaceItemInfo app1 = appPairIcon.getInfo().contents.get(0);
- WorkspaceItemInfo app2 = appPairIcon.getInfo().contents.get(1);
+ return;
+ } else if (info.isDisabled()) {
+ // App pair is disabled for another reason.
+ WorkspaceItemInfo app1 = info.getFirstApp();
+ WorkspaceItemInfo app2 = info.getSecondApp();
// Show the user why the app pair is disabled.
- if (app1.isDisabled() && !handleDisabledItemClicked(app1, launcher)) {
- // If handleDisabledItemClicked() did not handle the error message, we initiate an
- // app launch so Framework can tell the user why the app is suspended.
- onClickAppShortcut(v, app1, launcher);
- } else if (app2.isDisabled() && !handleDisabledItemClicked(app2, launcher)) {
- onClickAppShortcut(v, app2, launcher);
+ if (app1.isDisabled() && app2.isDisabled()) {
+ // Both apps are disabled, show generic "app pair is not available" toast.
+ Toast.makeText(launcher, R.string.app_pair_not_available, Toast.LENGTH_SHORT)
+ .show();
+ return;
+ } else if ((app1.isDisabled() && handleDisabledItemClicked(app1, launcher))
+ || (app2.isDisabled() && handleDisabledItemClicked(app2, launcher))) {
+ // Only one is disabled, and handleDisabledItemClicked() showed a specific toast
+ // explaining why, so we are done.
+ return;
}
- } else {
- launcher.launchAppPair(appPairIcon);
}
+
+ // Either the app pair is not disabled, or it is a disabled state that can be handled by
+ // framework directly (e.g. one app is paused), so go ahead and launch.
+ launcher.launchAppPair(icon);
}
/**
@@ -188,6 +216,9 @@
LauncherAppWidgetProviderInfo appWidgetInfo = new WidgetManagerHelper(launcher)
.findProvider(info.providerName, info.user);
if (appWidgetInfo == null) {
+ Log.e(TAG, "onClickPendingWidget: Pending widget ready for click setup,"
+ + " but LauncherAppWidgetProviderInfo was null. Returning."
+ + " component=" + info.getTargetComponent());
return;
}
WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
@@ -195,6 +226,10 @@
if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
// This should not happen, as we make sure that an Id is allocated during bind.
+ Log.e(TAG, "onClickPendingWidget: Pending widget ready for click setup,"
+ + " and LauncherAppWidgetProviderInfo was found. However,"
+ + " no appWidgetId was allocated. Returning."
+ + " component=" + info.getTargetComponent());
return;
}
addFlowHandler.startBindFlow(launcher, info.appWidgetId, info,
@@ -228,7 +263,7 @@
}
}
// Fallback to using custom market intent.
- Intent intent = ApiWrapper.getAppMarketActivityIntent(launcher,
+ Intent intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent(
packageName, Process.myUserHandle());
launcher.startActivitySafely(v, intent, item);
};
@@ -340,7 +375,7 @@
// Check for abandoned promise
if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()
- && (!Utilities.enableSupportForArchiving() || !shortcut.isArchived())) {
+ && (!Flags.enableSupportForArchiving() || !shortcut.isArchived())) {
String packageName = shortcut.getIntent().getComponent() != null
? shortcut.getIntent().getComponent().getPackageName()
: shortcut.getIntent().getPackage();
@@ -366,12 +401,12 @@
if (item instanceof ItemInfoWithIcon itemInfoWithIcon) {
if ((itemInfoWithIcon.runtimeStatusFlags
& ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
- intent = ApiWrapper.getAppMarketActivityIntent(launcher,
+ intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent(
itemInfoWithIcon.getTargetComponent().getPackageName(),
Process.myUserHandle());
- } else if ((itemInfoWithIcon.runtimeStatusFlags
- & ItemInfoWithIcon.FLAG_PRIVATE_SPACE_INSTALL_APP) != 0) {
- intent = ApiWrapper.getAppMarketActivityIntent(launcher,
+ } else if (itemInfoWithIcon.itemType
+ == LauncherSettings.Favorites.ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON) {
+ intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent(
BuildConfig.APPLICATION_ID,
launcher.getAppsView().getPrivateProfileManager().getProfileUser());
launcher.getStatsLogManager().logger().log(
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 116f13a..89057a2 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -39,6 +39,7 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.PrivateSpaceInstallAppButtonInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.views.BubbleTextHolder;
@@ -150,7 +151,10 @@
if (launcher.getWorkspace().isSwitchingState()) return false;
StatsLogger logger = launcher.getStatsLogManager().logger();
- if (v.getTag() instanceof ItemInfo) {
+ if (v.getTag() instanceof ItemInfo itemInfo) {
+ if (itemInfo instanceof PrivateSpaceInstallAppButtonInfo) {
+ return false;
+ }
logger.withItemInfo((ItemInfo) v.getTag());
}
logger.log(LAUNCHER_ALLAPPS_ITEM_LONG_PRESSED);
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index 405d2bb..b2d0d75 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -15,13 +15,13 @@
*/
package com.android.launcher3.util;
-import static com.android.launcher3.testing.shared.TestProtocol.GET_FROM_RECENTS_FAILURE;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
+import android.util.Log;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -31,6 +31,8 @@
*/
public final class ActivityTracker<T extends BaseActivity> {
+ private static final String TAG = "ActivityTracker";
+
private WeakReference<T> mCurrentActivity = new WeakReference<>(null);
private CopyOnWriteArrayList<SchedulerCallback<T>> mCallbacks = new CopyOnWriteArrayList<>();
@@ -41,9 +43,6 @@
public void onActivityDestroyed(T activity) {
if (mCurrentActivity.get() == activity) {
- testLogD(GET_FROM_RECENTS_FAILURE,
- String.format("ActivityTracker.onActivityDestroyed this=%s, activity=%s",
- this, activity));
mCurrentActivity.clear();
}
}
@@ -57,12 +56,13 @@
*
* @param callback The callback to call init() on when the activity is ready.
*/
- public void registerCallback(SchedulerCallback<T> callback) {
+ public void registerCallback(SchedulerCallback<T> callback, String reasonString) {
+ Log.d(TAG, "Registering callback: " + callback + ", reason=" + reasonString);
T activity = mCurrentActivity.get();
mCallbacks.add(callback);
if (activity != null) {
if (!callback.init(activity, activity.isStarted())) {
- unregisterCallback(callback);
+ unregisterCallback(callback, "ActivityTracker.registerCallback: Intent handled");
}
}
}
@@ -70,13 +70,12 @@
/**
* Unregisters a registered callback.
*/
- public void unregisterCallback(SchedulerCallback<T> callback) {
+ public void unregisterCallback(SchedulerCallback<T> callback, String reasonString) {
+ Log.d(TAG, "Unregistering callback: " + callback + ", reason=" + reasonString);
mCallbacks.remove(callback);
}
public boolean handleCreate(T activity) {
- testLogD(GET_FROM_RECENTS_FAILURE,
- String.format("ActivityTracker.handleCreate this=%s, activity=%s", this, activity));
mCurrentActivity = new WeakReference<>(activity);
return handleIntent(activity, false /* alreadyOnHome */);
}
@@ -87,16 +86,25 @@
private boolean handleIntent(T activity, boolean alreadyOnHome) {
boolean handled = false;
+ if (!mCallbacks.isEmpty()) {
+ Log.d(TAG, "handleIntent: mCallbacks=" + mCallbacks);
+ }
for (SchedulerCallback<T> cb : mCallbacks) {
if (!cb.init(activity, alreadyOnHome)) {
// Callback doesn't want any more updates
- unregisterCallback(cb);
+ unregisterCallback(cb, "ActivityTracker.handleIntent: Intent handled");
}
handled = true;
}
return handled;
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "ActivityTracker:");
+ writer.println(prefix + "\tmCurrentActivity=" + mCurrentActivity.get());
+ writer.println(prefix + "\tmCallbacks=" + mCallbacks);
+ }
+
public interface SchedulerCallback<T extends BaseActivity> {
/**
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
new file mode 100644
index 0000000..095518c
--- /dev/null
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_HOME_ROLE;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
+import android.app.ActivityOptions;
+import android.app.Person;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BuildConfig;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A wrapper for the hidden API calls
+ */
+public class ApiWrapper implements ResourceBasedOverride, SafeCloseable {
+
+ public static final MainThreadInitializedObject<ApiWrapper> INSTANCE =
+ forOverride(ApiWrapper.class, R.string.api_wrapper_class);
+
+ protected final Context mContext;
+
+ public ApiWrapper(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Returns the list of persons associated with the provided shortcut info
+ */
+ public Person[] getPersons(ShortcutInfo si) {
+ return Utilities.EMPTY_PERSON_ARRAY;
+ }
+
+ public Map<String, LauncherActivityInfo> getActivityOverrides() {
+ return Collections.emptyMap();
+ }
+
+ /**
+ * Creates an ActivityOptions to play fade-out animation on closing targets
+ */
+ public ActivityOptions createFadeOutAnimOptions() {
+ return ActivityOptions.makeCustomAnimation(mContext, 0, android.R.anim.fade_out);
+ }
+
+ /**
+ * Returns a map of all users on the device to their corresponding UI properties
+ */
+ public Map<UserHandle, UserIconInfo> queryAllUsers() {
+ UserManager um = mContext.getSystemService(UserManager.class);
+ Map<UserHandle, UserIconInfo> users = new ArrayMap<>();
+ List<UserHandle> usersActual = um.getUserProfiles();
+ if (usersActual != null) {
+ for (UserHandle user : usersActual) {
+ long serial = um.getSerialNumberForUser(user);
+
+ // Simple check to check if the provided user is work profile
+ // TODO: Migrate to a better platform API
+ NoopDrawable d = new NoopDrawable();
+ boolean isWork = (d != mContext.getPackageManager().getUserBadgedIcon(d, user));
+ UserIconInfo info = new UserIconInfo(
+ user,
+ isWork ? UserIconInfo.TYPE_WORK : UserIconInfo.TYPE_MAIN,
+ serial);
+ users.put(user, info);
+ }
+ }
+ return users;
+ }
+
+ /**
+ * Returns the list of the system packages that are installed at user creation.
+ * An empty list denotes that all system packages are installed for that user at creation.
+ */
+ public List<String> getPreInstalledSystemPackages(UserHandle user) {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Returns an intent which can be used to start the App Market activity (Installer
+ * Activity).
+ */
+ public Intent getAppMarketActivityIntent(String packageName, UserHandle user) {
+ return new Intent(Intent.ACTION_VIEW)
+ .setData(new Uri.Builder()
+ .scheme("market")
+ .authority("details")
+ .appendQueryParameter("id", packageName)
+ .build())
+ .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
+ .authority(BuildConfig.APPLICATION_ID).build());
+ }
+
+ /**
+ * Returns an intent which can be used to open Private Space Settings.
+ */
+ @Nullable
+ public Intent getPrivateSpaceSettingsIntent() {
+ return null;
+ }
+
+ /**
+ * Checks if an activity is flagged as non-resizeable.
+ */
+ public boolean isNonResizeableActivity(LauncherActivityInfo lai) {
+ // 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.
+ */
+ public void assignDefaultHomeRole(Context context) {
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ assert roleManager != null;
+ if (roleManager.isRoleAvailable(RoleManager.ROLE_HOME)
+ && !roleManager.isRoleHeld(RoleManager.ROLE_HOME)) {
+ Intent roleRequestIntent = roleManager.createRequestRoleIntent(
+ RoleManager.ROLE_HOME);
+ Launcher launcher = Launcher.getLauncher(context);
+ launcher.startActivityForResult(roleRequestIntent, REQUEST_HOME_ROLE);
+ }
+ }
+
+ @Override
+ public void close() { }
+
+ private static class NoopDrawable extends ColorDrawable {
+ @Override
+ public int getIntrinsicHeight() {
+ return 1;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return 1;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/BgObjectWithLooper.java b/src/com/android/launcher3/util/BgObjectWithLooper.java
deleted file mode 100644
index adc3c7d..0000000
--- a/src/com/android/launcher3/util/BgObjectWithLooper.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-
-import androidx.annotation.WorkerThread;
-
-import java.util.function.Consumer;
-
-/**
- * Utility class to define an object which does most of it's processing on a
- * dedicated background thread.
- */
-public abstract class BgObjectWithLooper {
-
- /**
- * Start initialization of the object
- */
- public final void initializeInBackground(String threadName) {
- new Thread(this::runOnThread, threadName).start();
- }
-
- private void runOnThread() {
- Looper.prepare();
- onInitialized(Looper.myLooper());
- Looper.loop();
- }
-
- /**
- * Called on the background thread to handle initialization
- */
- @WorkerThread
- protected abstract void onInitialized(Looper looper);
-
- /**
- * Helper method to create a content provider
- */
- protected static ContentObserver newContentObserver(Handler handler, Consumer<Uri> command) {
- return new ContentObserver(handler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- command.accept(uri);
- }
- };
- }
-}
diff --git a/src/com/android/launcher3/util/CannedAnimationCoordinator.kt b/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
index 85f81f5..749caac 100644
--- a/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
+++ b/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
@@ -113,10 +113,9 @@
// flags accordingly
animationController =
controller.apply {
- activity.stateManager.setCurrentAnimation(
- this,
- USER_CONTROLLED or HANDLE_STATE_APPLY
- )
+ activity
+ .stateManager
+ .setCurrentAnimation(this, USER_CONTROLLED or HANDLE_STATE_APPLY)
}
recreateAnimation(provider)
}
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index ff95212..16fabe2 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -19,7 +19,12 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_DESKTOP_MODE_KEY;
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
@@ -47,6 +52,7 @@
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.InvariantDeviceProfile.DeviceType;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
@@ -84,10 +90,11 @@
public static final int CHANGE_SUPPORTED_BOUNDS = 1 << 3;
public static final int CHANGE_NAVIGATION_MODE = 1 << 4;
public static final int CHANGE_TASKBAR_PINNING = 1 << 5;
+ public static final int CHANGE_DESKTOP_MODE = 1 << 6;
public static final int CHANGE_ALL = CHANGE_ACTIVE_SCREEN | CHANGE_ROTATION
| CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE
- | CHANGE_TASKBAR_PINNING;
+ | CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE;
private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
private static final String TARGET_OVERLAY_PACKAGE = "android";
@@ -141,33 +148,48 @@
private void attachTaskbarPinningSharedPreferenceChangeListener(Context context) {
mTaskbarPinningPreferenceChangeListener =
(sharedPreferences, key) -> {
- if (TASKBAR_PINNING_KEY.equals(key)
- && mInfo.mIsTaskbarPinned != LauncherPrefs.get(mContext).get(
- TASKBAR_PINNING)
- ) {
+ LauncherPrefs prefs = LauncherPrefs.get(mContext);
+ boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
+ && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
+ boolean isTaskbarPinningDesktopModeChanged =
+ TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
+ && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
+ TASKBAR_PINNING_IN_DESKTOP_MODE);
+ if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
handleInfoChange(mWindowContext.getDisplay());
}
};
LauncherPrefs.get(context).addListener(
mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
+ LauncherPrefs.get(context).addListener(
+ mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
}
/**
* Returns the current navigation mode
*/
public static NavigationMode getNavigationMode(Context context) {
- return INSTANCE.get(context).getInfo().navigationMode;
+ return INSTANCE.get(context).getInfo().getNavigationMode();
}
/**
- * Returns whether taskbar is transient.
+ * Returns whether taskbar is transient or persistent.
+ *
+ * @return {@code true} if transient, {@code false} if persistent.
*/
public static boolean isTransientTaskbar(Context context) {
return INSTANCE.get(context).getInfo().isTransientTaskbar();
}
/**
+ * Handles info change for desktop mode.
+ */
+ public static void handleInfoChangeForDesktopMode(Context context) {
+ INSTANCE.get(context).handleInfoChange(context.getDisplay());
+ }
+
+ /**
* Enables transient taskbar status for tests.
*/
@VisibleForTesting
@@ -188,6 +210,8 @@
if (enableTaskbarPinning()) {
LauncherPrefs.get(mContext).removeListener(
mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
+ LauncherPrefs.get(mContext).removeListener(
+ mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
}
if (mWindowContext != null) {
mWindowContext.unregisterComponentCallbacks(this);
@@ -280,7 +304,7 @@
Info newInfo = new Info(displayInfoContext, wmProxy, oldInfo.mPerDisplayBounds);
if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
- || newInfo.navigationMode != oldInfo.navigationMode) {
+ || newInfo.getNavigationMode() != oldInfo.getNavigationMode()) {
// Cache may not be valid anymore, recreate without cache
newInfo = new Info(displayInfoContext, wmProxy,
wmProxy.estimateInternalDisplayBounds(displayInfoContext));
@@ -296,7 +320,7 @@
if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale) {
change |= CHANGE_DENSITY;
}
- if (newInfo.navigationMode != oldInfo.navigationMode) {
+ if (newInfo.getNavigationMode() != oldInfo.getNavigationMode()) {
change |= CHANGE_NAVIGATION_MODE;
}
if (!newInfo.supportedBounds.equals(oldInfo.supportedBounds)
@@ -305,9 +329,15 @@
FileLog.w(TAG,
"(CHANGE_SUPPORTED_BOUNDS) perDisplayBounds: " + newInfo.mPerDisplayBounds);
}
- if (newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned) {
+ if ((newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned)
+ || (newInfo.mIsTaskbarPinnedInDesktopMode
+ != oldInfo.mIsTaskbarPinnedInDesktopMode)) {
change |= CHANGE_TASKBAR_PINNING;
}
+ if (newInfo.mIsInDesktopMode != oldInfo.mIsInDesktopMode) {
+ change |= CHANGE_DESKTOP_MODE;
+ }
+
if (DEBUG) {
Log.d(TAG, "handleInfoChange - change: " + getChangeFlagsString(change));
}
@@ -341,7 +371,7 @@
// Configuration property
public final float fontScale;
private final int densityDpi;
- public final NavigationMode navigationMode;
+ private final NavigationMode navigationMode;
private final PortraitSize mScreenSizeDp;
// WindowBounds
@@ -351,6 +381,9 @@
new ArrayMap<>();
private final boolean mIsTaskbarPinned;
+ private final boolean mIsTaskbarPinnedInDesktopMode;
+
+ private final boolean mIsInDesktopMode;
public Info(Context displayInfoContext) {
/* don't need system overrides for external displays */
@@ -374,7 +407,7 @@
navigationMode = wmProxy.getNavigationMode(displayInfoContext);
mPerDisplayBounds.putAll(perDisplayBoundsCache);
- List<WindowBounds> cachedValue = mPerDisplayBounds.get(normalizedDisplayInfo);
+ List<WindowBounds> cachedValue = getCurrentBounds();
realBounds = wmProxy.getRealBounds(displayInfoContext, displayInfo);
if (cachedValue == null) {
@@ -384,7 +417,7 @@
FileLog.e(TAG, "(Invalid Cache) perDisplayBounds : " + mPerDisplayBounds);
mPerDisplayBounds.clear();
mPerDisplayBounds.putAll(wmProxy.estimateInternalDisplayBounds(displayInfoContext));
- cachedValue = mPerDisplayBounds.get(normalizedDisplayInfo);
+ cachedValue = getCurrentBounds();
if (cachedValue == null) {
FileLog.e(TAG, "normalizedDisplayInfo not found in estimation: "
+ normalizedDisplayInfo);
@@ -410,6 +443,9 @@
}
mIsTaskbarPinned = LauncherPrefs.get(displayInfoContext).get(TASKBAR_PINNING);
+ mIsTaskbarPinnedInDesktopMode = LauncherPrefs.get(displayInfoContext).get(
+ TASKBAR_PINNING_IN_DESKTOP_MODE);
+ mIsInDesktopMode = wmProxy.isInDesktopMode();
}
/**
@@ -426,10 +462,14 @@
return sTransientTaskbarStatusForTests;
}
if (enableTaskbarPinning()) {
+ if (mIsInDesktopMode) {
+ return !mIsTaskbarPinnedInDesktopMode;
+ }
return !mIsTaskbarPinned;
}
return true;
}
+
/**
* Returns whether the taskbar is pinned in gesture navigation mode.
*/
@@ -437,6 +477,10 @@
return navigationMode == NavigationMode.NO_BUTTON && !isTransientTaskbar();
}
+ public boolean isInDesktopMode() {
+ return mIsInDesktopMode;
+ }
+
/**
* Returns {@code true} if the bounds represent a tablet.
*/
@@ -463,9 +507,33 @@
return Collections.unmodifiableSet(mPerDisplayBounds.keySet());
}
+ /**
+ * Returns all {@link WindowBounds}s for the current display.
+ */
+ public List<WindowBounds> getCurrentBounds() {
+ return mPerDisplayBounds.get(normalizedDisplayInfo);
+ }
+
public int getDensityDpi() {
return densityDpi;
}
+
+ public @DeviceType int getDeviceType() {
+ int flagPhone = 1 << 0;
+ int flagTablet = 1 << 1;
+
+ int type = supportedBounds.stream()
+ .mapToInt(bounds -> isTablet(bounds) ? flagTablet : flagPhone)
+ .reduce(0, (a, b) -> a | b);
+ if (type == (flagPhone | flagTablet)) {
+ // device has profiles supporting both phone and tablet modes
+ return TYPE_MULTI_DISPLAY;
+ } else if (type == flagTablet) {
+ return TYPE_TABLET;
+ } else {
+ return TYPE_PHONE;
+ }
+ }
}
/**
@@ -480,6 +548,7 @@
appendFlag(result, change, CHANGE_SUPPORTED_BOUNDS, "CHANGE_SUPPORTED_BOUNDS");
appendFlag(result, change, CHANGE_NAVIGATION_MODE, "CHANGE_NAVIGATION_MODE");
appendFlag(result, change, CHANGE_TASKBAR_PINNING, "CHANGE_TASKBAR_VARIANT");
+ appendFlag(result, change, CHANGE_DESKTOP_MODE, "CHANGE_DESKTOP_MODE");
return result.toString();
}
@@ -493,8 +562,10 @@
pw.println(" rotation=" + info.rotation);
pw.println(" fontScale=" + info.fontScale);
pw.println(" densityDpi=" + info.densityDpi);
- pw.println(" navigationMode=" + info.navigationMode.name());
+ pw.println(" navigationMode=" + info.getNavigationMode().name());
pw.println(" isTaskbarPinned=" + info.mIsTaskbarPinned);
+ pw.println(" isTaskbarPinnedInDesktopMode=" + info.mIsTaskbarPinnedInDesktopMode);
+ pw.println(" isInDesktopMode=" + info.mIsInDesktopMode);
pw.println(" currentSize=" + info.currentSize);
info.mPerDisplayBounds.forEach((key, value) -> pw.println(
" perDisplayBounds - " + key + ": " + value));
diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java
index e6ee186..fbdb5c2 100644
--- a/src/com/android/launcher3/util/DynamicResource.java
+++ b/src/com/android/launcher3/util/DynamicResource.java
@@ -22,7 +22,6 @@
import androidx.annotation.FractionRes;
import androidx.annotation.IntegerRes;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.ResourceProvider;
@@ -34,7 +33,8 @@
*
* To allow customization for a particular resource, add them to dynamic_resources.xml
*/
-public class DynamicResource implements ResourceProvider, PluginListener<ResourceProvider> {
+public class DynamicResource implements
+ ResourceProvider, PluginListener<ResourceProvider>, SafeCloseable {
private static final MainThreadInitializedObject<DynamicResource> INSTANCE =
new MainThreadInitializedObject<>(DynamicResource::new);
@@ -49,6 +49,11 @@
}
@Override
+ public void close() {
+ PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this);
+ }
+
+ @Override
public int getInt(@IntegerRes int resId) {
return mContext.getResources().getInteger(resId);
}
diff --git a/src/com/android/launcher3/util/FlagDebugUtils.kt b/src/com/android/launcher3/util/FlagDebugUtils.kt
index f281943..33b8330 100644
--- a/src/com/android/launcher3/util/FlagDebugUtils.kt
+++ b/src/com/android/launcher3/util/FlagDebugUtils.kt
@@ -2,6 +2,7 @@
import java.util.StringJoiner
import java.util.function.IntFunction
+import java.util.function.LongFunction
object FlagDebugUtils {
@@ -12,6 +13,13 @@
str.add(flagName)
}
}
+ /** Appends the [flagName] to [str] when the [flag] is set in [flags]. */
+ @JvmStatic
+ fun appendFlag(str: StringJoiner, flags: Long, flag: Long, flagName: String) {
+ if (flags and flag != 0L) {
+ str.add(flagName)
+ }
+ }
/**
* Produces a human-readable representation of the [current] flags, followed by a diff from from
@@ -34,4 +42,30 @@
}
return result.toString()
}
+
+ /**
+ * Produces a human-readable representation of the [current] flags, followed by a diff from from
+ * [previous].
+ *
+ * The resulting string is intented for logging and debugging.
+ */
+ @JvmStatic
+ fun formatFlagChange(
+ current: Long,
+ previous: Long,
+ flagSerializer: LongFunction<String>
+ ): String {
+ val result = StringJoiner(" ")
+ result.add("[" + flagSerializer.apply(current) + "]")
+ val changed = current xor previous
+ val added = current and changed
+ if (added != 0L) {
+ result.add("+[" + flagSerializer.apply(added) + "]")
+ }
+ val removed = previous and changed
+ if (removed != 0L) {
+ result.add("-[" + flagSerializer.apply(removed) + "]")
+ }
+ return result.toString()
+ }
}
diff --git a/src/com/android/launcher3/util/GridOccupancy.java b/src/com/android/launcher3/util/GridOccupancy.java
index 43e486c..f248e58 100644
--- a/src/com/android/launcher3/util/GridOccupancy.java
+++ b/src/com/android/launcher3/util/GridOccupancy.java
@@ -7,7 +7,7 @@
/**
* Utility object to manage the occupancy in a grid.
*/
-public class GridOccupancy extends AbsGridOccupancy {
+public class GridOccupancy {
private final int mCountX;
private final int mCountY;
@@ -30,7 +30,24 @@
* @return true if a vacant cell was found
*/
public boolean findVacantCell(int[] vacantOut, int spanX, int spanY) {
- return super.findVacantCell(vacantOut, cells, mCountX, mCountY, spanX, spanY);
+ for (int y = 0; (y + spanY) <= mCountY; y++) {
+ for (int x = 0; (x + spanX) <= mCountX; x++) {
+ boolean available = !cells[x][y];
+ out:
+ for (int i = x; i < x + spanX; i++) {
+ for (int j = y; j < y + spanY; j++) {
+ available = available && !cells[i][j];
+ if (!available) break out;
+ }
+ }
+ if (available) {
+ vacantOut[0] = x;
+ vacantOut[1] = y;
+ return true;
+ }
+ }
+ }
+ return false;
}
public void copyTo(GridOccupancy dest) {
diff --git a/src/com/android/launcher3/util/ItemInflater.kt b/src/com/android/launcher3/util/ItemInflater.kt
index cc66af1..ebf4656 100644
--- a/src/com/android/launcher3/util/ItemInflater.kt
+++ b/src/com/android/launcher3/util/ItemInflater.kt
@@ -29,6 +29,7 @@
import com.android.launcher3.apppairs.AppPairIcon
import com.android.launcher3.folder.FolderIcon
import com.android.launcher3.model.ModelWriter
+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
@@ -81,7 +82,8 @@
R.layout.app_pair_icon,
context,
parent,
- item as FolderInfo
+ item as AppPairInfo,
+ BubbleTextView.DISPLAY_WORKSPACE
)
Favorites.ITEM_TYPE_APPWIDGET,
Favorites.ITEM_TYPE_CUSTOM_APPWIDGET ->
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 3074111..063313a 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -65,20 +65,11 @@
* Returns a matcher for items within folders.
*/
public static Predicate<ItemInfo> forFolderMatch(Predicate<ItemInfo> childOperator) {
- return info -> info instanceof FolderInfo && ((FolderInfo) info).contents.stream()
+ return info -> info instanceof FolderInfo && ((FolderInfo) info).getContents().stream()
.anyMatch(childOperator);
}
/**
- * Returns a matcher for items within app pairs.
- */
- public static Predicate<ItemInfo> forAppPairMatch(Predicate<ItemInfo> childOperator) {
- Predicate<ItemInfo> isAppPair = info ->
- info instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_APP_PAIR;
- return isAppPair.and(forFolderMatch(childOperator));
- }
-
- /**
* Returns a matcher for items with provided ids
*/
public static Predicate<ItemInfo> ofItemIds(IntSet ids) {
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
index 69786bb..02779ce 100644
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
@@ -23,6 +23,7 @@
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;
@@ -59,7 +60,7 @@
: null);
} else if (info instanceof FolderInfo && v instanceof FolderIcon) {
((FolderIcon) v).updatePreviewItems(updates::contains);
- } else if (info instanceof FolderInfo && v instanceof AppPairIcon appPairIcon) {
+ } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
}
@@ -89,7 +90,7 @@
((PendingAppWidgetHostView) v).applyState();
} else if (v instanceof FolderIcon && info instanceof FolderInfo) {
((FolderIcon) v).updatePreviewItems(updates::contains);
- } else if (info instanceof FolderInfo && v instanceof AppPairIcon appPairIcon) {
+ } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
}
// process all the shortcuts
diff --git a/src/com/android/launcher3/util/LauncherLayoutBuilder.kt b/src/com/android/launcher3/util/LauncherLayoutBuilder.kt
new file mode 100644
index 0000000..ecc9953
--- /dev/null
+++ b/src/com/android/launcher3/util/LauncherLayoutBuilder.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util
+
+import android.util.Xml
+import com.android.launcher3.AutoInstallsLayout.ATTR_CLASS_NAME
+import com.android.launcher3.AutoInstallsLayout.ATTR_CONTAINER
+import com.android.launcher3.AutoInstallsLayout.ATTR_PACKAGE_NAME
+import com.android.launcher3.AutoInstallsLayout.ATTR_RANK
+import com.android.launcher3.AutoInstallsLayout.ATTR_SCREEN
+import com.android.launcher3.AutoInstallsLayout.ATTR_SHORTCUT_ID
+import com.android.launcher3.AutoInstallsLayout.ATTR_SPAN_X
+import com.android.launcher3.AutoInstallsLayout.ATTR_SPAN_Y
+import com.android.launcher3.AutoInstallsLayout.ATTR_TITLE
+import com.android.launcher3.AutoInstallsLayout.ATTR_TITLE_TEXT
+import com.android.launcher3.AutoInstallsLayout.ATTR_USER_TYPE
+import com.android.launcher3.AutoInstallsLayout.ATTR_X
+import com.android.launcher3.AutoInstallsLayout.ATTR_Y
+import com.android.launcher3.AutoInstallsLayout.TAG_APPWIDGET
+import com.android.launcher3.AutoInstallsLayout.TAG_AUTO_INSTALL
+import com.android.launcher3.AutoInstallsLayout.TAG_FOLDER
+import com.android.launcher3.AutoInstallsLayout.TAG_SHORTCUT
+import com.android.launcher3.AutoInstallsLayout.TAG_WORKSPACE
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.LauncherSettings.Favorites.containerToString
+import java.io.IOException
+import java.io.StringWriter
+import java.io.Writer
+import org.xmlpull.v1.XmlSerializer
+
+/** Helper class to build xml for Launcher Layout */
+class LauncherLayoutBuilder {
+ private val nodes = ArrayList<Node>()
+
+ fun atHotseat(rank: Int) =
+ ItemTarget(
+ mapOf(
+ ATTR_CONTAINER to containerToString(CONTAINER_HOTSEAT),
+ ATTR_RANK to rank.toString()
+ )
+ )
+
+ fun atWorkspace(x: Int, y: Int, screen: Int) =
+ ItemTarget(
+ mapOf(
+ ATTR_CONTAINER to containerToString(CONTAINER_DESKTOP),
+ ATTR_X to x.toString(),
+ ATTR_Y to y.toString(),
+ ATTR_SCREEN to screen.toString()
+ )
+ )
+
+ @Throws(IOException::class) fun build() = StringWriter().apply { build(this) }.toString()
+
+ @Throws(IOException::class)
+ fun build(writer: Writer) {
+ Xml.newSerializer().apply {
+ setOutput(writer)
+ setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
+ startDocument("UTF-8", true)
+ startTag(null, TAG_WORKSPACE)
+ writeNodes(nodes)
+ endTag(null, TAG_WORKSPACE)
+ endDocument()
+ flush()
+ }
+ }
+
+ open inner class ItemTarget(private val baseValues: Map<String, String>) {
+ @JvmOverloads
+ fun putApp(packageName: String, className: String?, userType: String? = null) =
+ addItem(
+ TAG_AUTO_INSTALL,
+ userType,
+ mapOf(
+ ATTR_PACKAGE_NAME to packageName,
+ ATTR_CLASS_NAME to (className ?: packageName)
+ )
+ )
+
+ @JvmOverloads
+ fun putShortcut(packageName: String, shortcutId: String, userType: String? = null) =
+ addItem(
+ TAG_SHORTCUT,
+ userType,
+ mapOf(ATTR_PACKAGE_NAME to packageName, ATTR_SHORTCUT_ID to shortcutId)
+ )
+
+ @JvmOverloads
+ fun putWidget(
+ packageName: String,
+ className: String,
+ spanX: Int,
+ spanY: Int,
+ userType: String? = null
+ ) =
+ addItem(
+ TAG_APPWIDGET,
+ userType,
+ mapOf(
+ ATTR_PACKAGE_NAME to packageName,
+ ATTR_CLASS_NAME to className,
+ ATTR_SPAN_X to spanX.toString(),
+ ATTR_SPAN_Y to spanY.toString()
+ )
+ )
+
+ fun putFolder(titleResId: Int) = putFolder(ATTR_TITLE, titleResId.toString())
+
+ fun putFolder(title: String?) = putFolder(ATTR_TITLE_TEXT, title)
+
+ protected open fun addItem(
+ tag: String,
+ userType: String?,
+ props: Map<String, String>,
+ children: List<Node>? = null
+ ): LauncherLayoutBuilder {
+ nodes.add(
+ Node(
+ tag,
+ HashMap(baseValues).apply {
+ putAll(props)
+ userType?.let { put(ATTR_USER_TYPE, it) }
+ },
+ children
+ )
+ )
+ return this@LauncherLayoutBuilder
+ }
+
+ protected open fun putFolder(titleKey: String, titleValue: String?): FolderBuilder {
+ val folderBuilder = FolderBuilder()
+ addItem(TAG_FOLDER, null, mapOf(titleKey to (titleValue ?: "")), folderBuilder.children)
+ return folderBuilder
+ }
+ }
+
+ inner class FolderBuilder : ItemTarget(mapOf()) {
+
+ val children = ArrayList<Node>()
+
+ fun addApp(packageName: String, className: String?): FolderBuilder {
+ putApp(packageName, className)
+ return this
+ }
+
+ fun addShortcut(packageName: String, shortcutId: String): FolderBuilder {
+ putShortcut(packageName, shortcutId)
+ return this
+ }
+
+ override fun addItem(
+ tag: String,
+ userType: String?,
+ props: Map<String, String>,
+ childrenIgnored: List<Node>?
+ ): LauncherLayoutBuilder {
+ children.add(
+ Node(tag, HashMap(props).apply { userType?.let { put(ATTR_USER_TYPE, it) } })
+ )
+ return this@LauncherLayoutBuilder
+ }
+
+ override fun putFolder(titleKey: String, titleValue: String?): FolderBuilder {
+ throw IllegalArgumentException("Can't have folder inside a folder")
+ }
+
+ fun build() = this@LauncherLayoutBuilder
+ }
+
+ @Throws(IOException::class)
+ private fun XmlSerializer.writeNodes(nodes: List<Node>) {
+ nodes.forEach { node ->
+ startTag(null, node.name)
+ node.attrs.forEach { (key, value) -> attribute(null, key, value) }
+ node.children?.let { writeNodes(it) }
+ endTag(null, node.name)
+ }
+ }
+
+ data class Node(
+ val name: String,
+ val attrs: Map<String, String>,
+ val children: List<Node>? = null
+ )
+}
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index 0a87594..94f9e4f 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -56,12 +56,16 @@
private fun notifyUserUnlocked() {
mUserUnlockedActions.executeAllAndDestroy()
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+ Executors.THREAD_POOL_EXECUTOR.execute {
+ mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+ }
}
/** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
override fun close() {
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+ Executors.THREAD_POOL_EXECUTOR.execute {
+ mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+ }
}
/**
diff --git a/src/com/android/launcher3/util/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
index e5bbcb1..3d4b409 100644
--- a/src/com/android/launcher3/util/LogConfig.java
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -65,4 +65,15 @@
* When turned on, we enable AGA related session summary logging.
*/
public static final String AGA_SESSION_SUMMARY_LOG = "AGASessionSummaryLog";
+
+ /**
+ * When turned on, we enable long press nav handle related logging.
+ */
+ public static final String NAV_HANDLE_LONG_PRESS = "NavHandleLongPress";
+
+
+ /**
+ * When turned on, we enable zero state web data loader related logging.
+ */
+ public static final String ZERO_WEB_DATA_LOADER = "ZeroStateWebDataLoaderLog";
}
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index b966d8e..1a0f9a0 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -28,17 +28,15 @@
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
/**
* Utility class for defining singletons which are initiated on main thread.
*/
-public class MainThreadInitializedObject<T> {
+public class MainThreadInitializedObject<T extends SafeCloseable> {
private final ObjectProvider<T> mProvider;
private T mValue;
@@ -48,14 +46,14 @@
}
public T get(Context context) {
- if (context instanceof SandboxContext sc) {
+ Context app = context.getApplicationContext();
+ if (app instanceof SandboxApplication sc) {
return sc.getObject(this);
}
if (mValue == null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
- mValue = TraceHelper.allowIpcs("main.thread.object",
- () -> mProvider.get(context.getApplicationContext()));
+ mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
} else {
try {
return MAIN_EXECUTOR.submit(() -> get(context)).get();
@@ -67,8 +65,18 @@
return mValue;
}
- public T getNoCreate() {
- return mValue;
+ /**
+ * Executes the callback is the value is already created
+ * @return true if the callback was executed, false otherwise
+ */
+ public boolean executeIfCreated(Consumer<T> callback) {
+ T v = mValue;
+ if (v != null) {
+ callback.accept(v);
+ return true;
+ } else {
+ return false;
+ }
}
@VisibleForTesting
@@ -79,8 +87,8 @@
/**
* Initializes a provider based on resource overrides
*/
- public static <T extends ResourceBasedOverride> MainThreadInitializedObject<T> forOverride(
- Class<T> clazz, int resourceId) {
+ public static <T extends ResourceBasedOverride & SafeCloseable> MainThreadInitializedObject<T>
+ forOverride(Class<T> clazz, int resourceId) {
return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId));
}
@@ -89,24 +97,36 @@
T get(Context context);
}
+ public interface SandboxApplication {
+
+ /**
+ * Find a cached object from mObjectMap if we have already created one. If not, generate
+ * an object using the provider.
+ */
+ <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
+
+ @UiThread
+ default <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
+ return object.mProvider.get((Context) this);
+ }
+ }
+
/**
* Abstract Context which allows custom implementations for
* {@link MainThreadInitializedObject} providers
*/
- public static class SandboxContext extends ContextWrapper {
+ public static class SandboxContext extends ContextWrapper implements SandboxApplication {
private static final String TAG = "SandboxContext";
- protected final Set<MainThreadInitializedObject> mAllowedObjects;
- protected final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
- protected final ArrayList<Object> mOrderedObjects = new ArrayList<>();
+ private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
+ private final ArrayList<SafeCloseable> mOrderedObjects = new ArrayList<>();
private final Object mDestroyLock = new Object();
private boolean mDestroyed = false;
- public SandboxContext(Context base, MainThreadInitializedObject... allowedObjects) {
+ public SandboxContext(Context base) {
super(base);
- mAllowedObjects = new HashSet<>(Arrays.asList(allowedObjects));
}
@Override
@@ -118,20 +138,14 @@
synchronized (mDestroyLock) {
// Destroy in reverse order
for (int i = mOrderedObjects.size() - 1; i >= 0; i--) {
- Object o = mOrderedObjects.get(i);
- if (o instanceof SafeCloseable) {
- ((SafeCloseable) o).close();
- }
+ mOrderedObjects.get(i).close();
}
mDestroyed = true;
}
}
- /**
- * Find a cached object from mObjectMap if we have already created one. If not, generate
- * an object using the provider.
- */
- protected <T> T getObject(MainThreadInitializedObject<T> object) {
+ @Override
+ public <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object) {
synchronized (mDestroyLock) {
if (mDestroyed) {
Log.e(TAG, "Static object access with a destroyed context");
@@ -142,12 +156,6 @@
}
if (Looper.myLooper() == Looper.getMainLooper()) {
t = createObject(object);
- // Check if we've explicitly allowed the object or if it's a SafeCloseable,
- // it will get destroyed in onDestroy()
- if (!mAllowedObjects.contains(object) && !(t instanceof SafeCloseable)) {
- throw new IllegalStateException("Leaking unknown objects "
- + object + " " + object.mProvider + " " + t);
- }
mObjectMap.put(object, t);
mOrderedObjects.add(t);
return t;
@@ -161,17 +169,12 @@
}
}
- @UiThread
- protected <T> T createObject(MainThreadInitializedObject<T> object) {
- return object.mProvider.get(this);
- }
-
/**
* Put a value into mObjectMap, can be used to put mocked MainThreadInitializedObject
* instances into SandboxContext.
*/
- @VisibleForTesting
- public <T> void putObject(MainThreadInitializedObject<T> object, T value) {
+ public <T extends SafeCloseable> void putObject(
+ MainThreadInitializedObject<T> object, T value) {
mObjectMap.put(object, value);
}
}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.kt b/src/com/android/launcher3/util/OnboardingPrefs.kt
index 4528cba..ac6e97c 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.kt
+++ b/src/com/android/launcher3/util/OnboardingPrefs.kt
@@ -75,4 +75,6 @@
@JvmField
val HOTSEAT_LONGPRESS_TIP_SEEN = backedUpItem("launcher.hotseat_longpress_tip_seen", false)
+
+ @JvmField val TASKBAR_SEARCH_EDU_SEEN = backedUpItem("launcher.taskbar_search_edu_seen", false)
}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 11d8e97..8c5a76e 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,6 +16,10 @@
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;
import android.content.ComponentName;
import android.content.Context;
@@ -38,6 +42,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.Flags;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -46,20 +51,22 @@
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.uioverrides.ApiWrapper;
-import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
/**
* Utility methods using package manager
*/
-public class PackageManagerHelper {
+public class PackageManagerHelper implements SafeCloseable{
private static final String TAG = "PackageManagerHelper";
@NonNull
+ public static final MainThreadInitializedObject<PackageManagerHelper> INSTANCE =
+ new MainThreadInitializedObject<>(PackageManagerHelper::new);
+
+ @NonNull
private final Context mContext;
@NonNull
@@ -68,12 +75,19 @@
@NonNull
private final LauncherApps mLauncherApps;
+ private final String[] mLegacyMultiInstanceSupportedApps;
+
public PackageManagerHelper(@NonNull final Context context) {
mContext = context;
mPm = context.getPackageManager();
mLauncherApps = Objects.requireNonNull(context.getSystemService(LauncherApps.class));
+ mLegacyMultiInstanceSupportedApps = mContext.getResources().getStringArray(
+ R.array.config_appsSupportMultiInstancesSplit);
}
+ @Override
+ public void close() { }
+
/**
* Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
* guarantee that the app is on SD card.
@@ -105,6 +119,22 @@
}
/**
+ * Returns whether the target app is archived for a given user
+ */
+ @SuppressWarnings("NewApi")
+ public boolean isAppArchivedForUser(@NonNull final String packageName,
+ @NonNull final UserHandle user) {
+ if (!Flags.enableSupportForArchiving()) {
+ return false;
+ }
+ final ApplicationInfo info = getApplicationInfo(
+ // LauncherApps does not support long flags currently. Since archived apps are
+ // subset of uninstalled apps, this filter also includes archived apps.
+ packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ return info != null && info.isArchived;
+ }
+
+ /**
* Returns whether the target app is in archived state
*/
@SuppressWarnings("NewApi")
@@ -122,6 +152,18 @@
}
/**
+ * Returns the installing app package for the given package
+ */
+ public String getAppInstallerPackage(@NonNull final String packageName) {
+ try {
+ return mPm.getInstallSourceInfo(packageName).getInstallingPackageName();
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Failed to get installer package for app package:" + packageName, e);
+ return null;
+ }
+ }
+
+ /**
* Returns the application info for the provided package or null
*/
@Nullable
@@ -135,11 +177,23 @@
}
}
+ /**
+ * Returns the preferred launch activity intent for a given package.
+ */
@Nullable
public Intent getAppLaunchIntent(@Nullable final String pkg, @NonNull final UserHandle user) {
+ LauncherActivityInfo info = getAppLaunchInfo(pkg, user);
+ return info != null ? AppInfo.makeLaunchIntent(info) : null;
+ }
+
+ /**
+ * Returns the preferred launch activity for a given package.
+ */
+ @Nullable
+ public LauncherActivityInfo getAppLaunchInfo(@Nullable final String pkg,
+ @NonNull final UserHandle user) {
List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user);
- return activities.isEmpty() ? null :
- AppInfo.makeLaunchIntent(activities.get(0));
+ return activities.isEmpty() ? null : activities.get(0);
}
/**
@@ -151,30 +205,13 @@
}
/**
- * Creates a new market search intent.
- */
- public static Intent getMarketSearchIntent(Context context, String query) {
- try {
- Intent intent = Intent.parseUri(context.getString(R.string.market_search_intent), 0);
- if (!TextUtils.isEmpty(query)) {
- intent.setData(
- intent.getData().buildUpon().appendQueryParameter("q", query).build());
- }
- return intent;
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
* Starts the details activity for {@code info}
*/
- public void startDetailsActivityForInfo(ItemInfo info, Rect sourceBounds, Bundle opts) {
- if (info instanceof ItemInfoWithIcon
- && (((ItemInfoWithIcon) info).runtimeStatusFlags
- & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
- ItemInfoWithIcon appInfo = (ItemInfoWithIcon) info;
- mContext.startActivity(ApiWrapper.getAppMarketActivityIntent(mContext,
+ public static void startDetailsActivityForInfo(Context context, ItemInfo info,
+ Rect sourceBounds, Bundle opts) {
+ if (info instanceof ItemInfoWithIcon appInfo
+ && (appInfo.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) {
+ context.startActivity(ApiWrapper.INSTANCE.get(context).getAppMarketActivityIntent(
appInfo.getTargetComponent().getPackageName(), Process.myUserHandle()));
return;
}
@@ -190,9 +227,10 @@
}
if (componentName != null) {
try {
- mLauncherApps.startAppDetailsActivity(componentName, info.user, sourceBounds, opts);
+ context.getSystemService(LauncherApps.class).startAppDetailsActivity(componentName,
+ info.user, sourceBounds, opts);
} catch (SecurityException | ActivityNotFoundException e) {
- Toast.makeText(mContext, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to launch settings", e);
}
}
@@ -251,6 +289,7 @@
/**
* Returns true if Launcher has the permission to access shortcuts.
+ *
* @see LauncherApps#hasShortcutHostPermission()
*/
public static boolean hasShortcutsPermission(Context context) {
@@ -274,6 +313,49 @@
@SuppressWarnings("NewApi")
private boolean isPackageInstalledOrArchived(ApplicationInfo info) {
return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 || (
- Utilities.enableSupportForArchiving() && info.isArchived);
+ Flags.enableSupportForArchiving() && info.isArchived);
+ }
+
+ /**
+ * 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.
+ */
+ public static boolean isSameAppForMultiInstance(@NonNull ItemInfo app1,
+ @NonNull ItemInfo app2) {
+ return app1.getTargetPackage().equals(app2.getTargetPackage())
+ && app1.user.equals(app2.user);
}
}
diff --git a/src/com/android/launcher3/util/PluginManagerWrapper.java b/src/com/android/launcher3/util/PluginManagerWrapper.java
new file mode 100644
index 0000000..b27aa12
--- /dev/null
+++ b/src/com/android/launcher3/util/PluginManagerWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
+import com.android.launcher3.R;
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginListener;
+
+import java.io.PrintWriter;
+
+public class PluginManagerWrapper implements ResourceBasedOverride, SafeCloseable {
+
+ public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
+ forOverride(PluginManagerWrapper.class, R.string.plugin_manager_wrapper_class);
+
+ public <T extends Plugin> void addPluginListener(
+ PluginListener<T> listener, Class<T> pluginClass) {
+ addPluginListener(listener, pluginClass, false);
+ }
+
+ public <T extends Plugin> void addPluginListener(
+ PluginListener<T> listener, Class<T> pluginClass, boolean allowMultiple) {
+ }
+
+ public void removePluginListener(PluginListener<? extends Plugin> listener) { }
+
+ @Override
+ public void close() { }
+
+ public void dump(PrintWriter pw) { }
+}
diff --git a/src/com/android/launcher3/util/ResourceBasedOverride.java b/src/com/android/launcher3/util/ResourceBasedOverride.java
index e2c4992..36b9cf7 100644
--- a/src/com/android/launcher3/util/ResourceBasedOverride.java
+++ b/src/com/android/launcher3/util/ResourceBasedOverride.java
@@ -34,16 +34,20 @@
public static <T extends ResourceBasedOverride> T getObject(
Class<T> clazz, Context context, int resId) {
String className = context.getString(resId);
- if (!TextUtils.isEmpty(className)) {
- try {
- Class<?> cls = Class.forName(className);
- return (T) cls.getDeclaredConstructor(Context.class).newInstance(context);
- } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
- | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
+ boolean isOverridden = !TextUtils.isEmpty(className);
+
+ // First try to load the class with "Context" param
+ try {
+ Class<?> cls = isOverridden ? Class.forName(className) : clazz;
+ return (T) cls.getDeclaredConstructor(Context.class).newInstance(context);
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+ | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
+ if (isOverridden) {
Log.e(TAG, "Bad overriden class", e);
}
}
+ // Load the base class with no parameter
try {
return clazz.newInstance();
} catch (InstantiationException|IllegalAccessException e) {
diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java
index 67530a6..e16e477 100644
--- a/src/com/android/launcher3/util/ScreenOnTracker.java
+++ b/src/com/android/launcher3/util/ScreenOnTracker.java
@@ -27,7 +27,7 @@
/**
* Utility class for tracking if the screen is currently on or off
*/
-public class ScreenOnTracker {
+public class ScreenOnTracker implements SafeCloseable {
public static final MainThreadInitializedObject<ScreenOnTracker> INSTANCE =
new MainThreadInitializedObject<>(ScreenOnTracker::new);
@@ -35,14 +35,21 @@
private final SimpleBroadcastReceiver mReceiver = new SimpleBroadcastReceiver(this::onReceive);
private final CopyOnWriteArrayList<ScreenOnListener> mListeners = new CopyOnWriteArrayList<>();
+ private final Context mContext;
private boolean mIsScreenOn;
private ScreenOnTracker(Context context) {
// Assume that the screen is on to begin with
+ mContext = context;
mIsScreenOn = true;
mReceiver.register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
}
+ @Override
+ public void close() {
+ mReceiver.unregisterReceiverSafely(mContext);
+ }
+
private void onReceive(Intent intent) {
String action = intent.getAction();
if (ACTION_SCREEN_ON.equals(action)) {
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
index 91cf835..07b7941 100644
--- a/src/com/android/launcher3/util/ShortcutUtil.java
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -15,9 +15,10 @@
*/
package com.android.launcher3.util;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
+
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
-import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -34,7 +35,7 @@
* Returns true when we should show depp shortcuts in shortcut menu for the item.
*/
public static boolean supportsDeepShortcuts(ItemInfo info) {
- return isActive(info) && isApp(info) && !WidgetsModel.GO_DISABLE_WIDGETS;
+ return isActive(info) && isApp(info) && !!WIDGETS_ENABLED;
}
/**
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 837d7bc..95624b1 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -186,6 +186,12 @@
public int stagePosition = STAGE_POSITION_UNDEFINED;
@StageType
public int stageType = STAGE_TYPE_UNDEFINED;
+
+ @Override
+ public String toString() {
+ return "SplitStageInfo { taskId=" + taskId
+ + ", stagePosition=" + stagePosition + ", stageType=" + stageType + " }";
+ }
}
public static StatsLogManager.EventEnum getLogEventForPosition(@StagePosition int position) {
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index e1695e9..51749a7 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -19,19 +19,12 @@
import static android.os.VibrationEffect.createPredefined;
import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_DELAY;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.annotation.SuppressLint;
-import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
import android.media.AudioAttributes;
+import android.net.Uri;
import android.os.SystemClock;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -40,12 +33,11 @@
import androidx.annotation.Nullable;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
/**
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
*/
-public class VibratorWrapper {
+public class VibratorWrapper implements SafeCloseable {
public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
new MainThreadInitializedObject<>(VibratorWrapper::new);
@@ -57,6 +49,8 @@
public static final VibrationEffect EFFECT_CLICK =
createPredefined(VibrationEffect.EFFECT_CLICK);
+ private static final Uri HAPTIC_FEEDBACK_URI =
+ Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED);
private static final float LOW_TICK_SCALE = 0.9f;
private static final float DRAG_TEXTURE_SCALE = 0.03f;
@@ -71,9 +65,6 @@
@Nullable
private final VibrationEffect mBumpEffect;
- @Nullable
- private final VibrationEffect mSearchEffect;
-
private long mLastDragTime;
private final int mThresholdUntilNextDragCallMillis;
@@ -85,6 +76,8 @@
private final Context mContext;
private final Vibrator mVibrator;
private final boolean mHasVibrator;
+ private final SettingsCache.OnChangeListener mHapticChangeListener =
+ isEnabled -> mIsHapticFeedbackEnabled = isEnabled;
private boolean mIsHapticFeedbackEnabled;
@@ -93,16 +86,9 @@
mVibrator = context.getSystemService(Vibrator.class);
mHasVibrator = mVibrator.hasVibrator();
if (mHasVibrator) {
- final ContentResolver resolver = context.getContentResolver();
- mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver);
- final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
- @Override
- public void onChange(boolean selfChange) {
- mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver);
- }
- };
- resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED),
- false /* notifyForDescendants */, observer);
+ SettingsCache cache = SettingsCache.INSTANCE.get(mContext);
+ cache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener);
+ mIsHapticFeedbackEnabled = cache.getValue(HAPTIC_FEEDBACK_URI, 0);
} else {
mIsHapticFeedbackEnabled = false;
}
@@ -133,24 +119,13 @@
mBumpEffect = null;
mThresholdUntilNextDragCallMillis = 0;
}
+ }
- if (mVibrator.areAllPrimitivesSupported(
- VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
- VibrationEffect.Composition.PRIMITIVE_TICK)) {
- if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get()) {
- mSearchEffect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f)
- .compose();
- } else {
- // quiet ramp, short pause, then sharp tick
- mSearchEffect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
- .compose();
- }
- } else {
- // fallback for devices without composition support
- mSearchEffect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK);
+ @Override
+ public void close() {
+ if (mHasVibrator) {
+ SettingsCache.INSTANCE.get(mContext)
+ .unregister(HAPTIC_FEEDBACK_URI, mHapticChangeListener);
}
}
@@ -203,10 +178,6 @@
mLastDragTime = 0;
}
- private boolean isHapticFeedbackEnabled(ContentResolver resolver) {
- return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1;
- }
-
/** Vibrates with the given effect if haptic feedback is available and enabled. */
public void vibrate(VibrationEffect vibrationEffect) {
if (mHasVibrator && mIsHapticFeedbackEnabled) {
@@ -233,13 +204,6 @@
}
}
- /** Indicates that search has been invoked. */
- public void vibrateForSearch() {
- if (mSearchEffect != null) {
- vibrate(mSearchEffect);
- }
- }
-
/** Indicates that Taskbar has been invoked. */
public void vibrateForTaskbarUnstash() {
if (Utilities.ATLEAST_S && mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
@@ -251,32 +215,4 @@
vibrate(primitiveLowTickEffect);
}
}
-
- /** Indicates that search will be invoked if the current gesture is maintained. */
- public void vibrateForSearchHint() {
- if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get() && Utilities.ATLEAST_S
- && mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
- float startScale = LPNH_HAPTIC_HINT_START_SCALE_PERCENT.get() / 100f;
- float endScale = LPNH_HAPTIC_HINT_END_SCALE_PERCENT.get() / 100f;
- int scaleExponent = LPNH_HAPTIC_HINT_SCALE_EXPONENT.get();
- int iterations = LPNH_HAPTIC_HINT_ITERATIONS.get();
- int delayMs = LPNH_HAPTIC_HINT_DELAY.get();
-
- VibrationEffect.Composition composition = VibrationEffect.startComposition();
- for (int i = 0; i < iterations; i++) {
- float t = i / (iterations - 1f);
- float scale = (float) Math.pow((1 - t) * startScale + t * endScale,
- scaleExponent);
- if (i == 0) {
- // Adds a delay before the ramp starts
- composition.addPrimitive(PRIMITIVE_LOW_TICK, scale,
- delayMs);
- } else {
- composition.addPrimitive(PRIMITIVE_LOW_TICK, scale);
- }
- }
-
- vibrate(composition.compose());
- }
- }
}
diff --git a/src/com/android/launcher3/util/rects/Rects.kt b/src/com/android/launcher3/util/rects/Rects.kt
new file mode 100644
index 0000000..1e6d717
--- /dev/null
+++ b/src/com/android/launcher3/util/rects/Rects.kt
@@ -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.
+ */
+
+package com.android.launcher3.util.rects
+
+import android.graphics.Rect
+import android.view.View
+
+/** Copy the coordinates of the [view] relative to its parent into this rectangle. */
+fun Rect.set(view: View) {
+ set(0, 0, view.width, view.height)
+ offset(view.x.toInt(), view.y.toInt())
+}
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 4a906d3..0817c0a 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -59,6 +59,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.WindowBounds;
import java.util.ArrayList;
@@ -67,7 +68,7 @@
/**
* Utility class for mocking some window manager behaviours
*/
-public class WindowManagerProxy implements ResourceBasedOverride {
+public class WindowManagerProxy implements ResourceBasedOverride, SafeCloseable {
private static final String TAG = "WindowManagerProxy";
public static final int MIN_TABLET_WIDTH = 600;
@@ -94,6 +95,13 @@
}
/**
+ * Returns true if taskbar is drawn in process
+ */
+ public boolean isTaskbarDrawnInProcess() {
+ return mTaskbarDrawnInProcess;
+ }
+
+ /**
* Returns a map of normalized info of internal displays to estimated window bounds
* for that display
*/
@@ -107,6 +115,13 @@
}
/**
+ * Returns if we are in desktop mode or not.
+ */
+ public boolean isInDesktopMode() {
+ return false;
+ }
+
+ /**
* Returns the real bounds for the provided display after applying any insets normalization
*/
public WindowBounds getRealBounds(Context displayInfoContext, CachedDisplayInfo info) {
@@ -298,12 +313,12 @@
navBarHeightPortrait = isTablet
? (mTaskbarDrawnInProcess
- ? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
+ ? 0 : context.getResources().getDimensionPixelSize(R.dimen.taskbar_size))
: getDimenByName(systemRes, NAVBAR_HEIGHT);
navBarHeightLandscape = isTablet
? (mTaskbarDrawnInProcess
- ? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
+ ? 0 : context.getResources().getDimensionPixelSize(R.dimen.taskbar_size))
: (isTabletOrGesture
? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) : 0);
navbarWidthLandscape = isTabletOrGesture
@@ -467,6 +482,9 @@
NavigationMode.THREE_BUTTONS;
}
+ @Override
+ public void close() { }
+
/**
* @see DisplayCutout#getSafeInsets
*/
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 30e0971..85aad89 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -25,6 +25,8 @@
import static com.android.launcher3.allapps.AllAppsTransitionController.REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS;
import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE;
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -82,7 +84,6 @@
};
protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
protected static final float TRANSLATION_SHIFT_OPENED = 0f;
- private static final float VIEW_NO_SCALE = 1f;
private static final int DEFAULT_DURATION = 300;
protected final T mActivityContext;
@@ -91,7 +92,7 @@
protected @NonNull AnimatorPlaybackController mOpenCloseAnimation;
protected ViewGroup mContent;
- protected final View mColorScrim;
+ protected final @Nullable View mColorScrim;
/**
* Interpolator for {@link #mOpenCloseAnimation} when we are closing due to dragging downwards.
@@ -129,9 +130,14 @@
protected @Nullable OnCloseListener mOnCloseBeginListener;
protected List<OnCloseListener> mOnCloseListeners = new ArrayList<>();
- protected final AnimatedFloat mSlideInViewScale =
- new AnimatedFloat(this::onScaleProgressChanged, VIEW_NO_SCALE);
- protected boolean mIsBackProgressing;
+ /**
+ * How far through a "user initiated dismissal" the UI is. e.g. Predictive back, swipe to home,
+ * 0 is regular state, 1 is fully dismissed.
+ */
+ protected final AnimatedFloat mSwipeToDismissProgress =
+ new AnimatedFloat(this::onUserSwipeToDismissProgressChanged, 0f);
+ protected boolean mIsDismissInProgress;
+ private View mViewToAnimateInSwipeToDismiss = this;
private @Nullable Drawable mContentBackground;
private @Nullable View mContentBackgroundParentView;
@@ -210,6 +216,9 @@
animation.addFloat(
this, TRANSLATION_SHIFT, fromTranslationShift, toTranslationShift, LINEAR);
+ if (mColorScrim != null) {
+ animation.setViewAlpha(mColorScrim, 1 - toTranslationShift, getScrimInterpolator());
+ }
onOpenCloseAnimationPending(animation);
mOpenCloseAnimation = animation.createPlaybackController();
@@ -248,9 +257,6 @@
protected void setTranslationShift(float translationShift) {
mTranslationShift = translationShift;
mContent.setTranslationY(mTranslationShift * getShiftRange());
- if (mColorScrim != null) {
- mColorScrim.setAlpha(1 - mTranslationShift);
- }
invalidate();
}
@@ -283,39 +289,77 @@
@Override
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- public void onBackProgressed(BackEvent backEvent) {
- final float progress = backEvent.getProgress();
- float deceleratedProgress =
- Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(progress);
- mIsBackProgressing = progress > 0f;
- mSlideInViewScale.updateValue(PREDICTIVE_BACK_MIN_SCALE
- + (1 - PREDICTIVE_BACK_MIN_SCALE) * (1 - deceleratedProgress));
- }
-
- protected void onScaleProgressChanged() {
- float scaleProgress = mSlideInViewScale.value;
- SCALE_PROPERTY.set(this, scaleProgress);
- setClipChildren(!mIsBackProgressing);
- mContent.setClipChildren(!mIsBackProgressing);
- invalidate();
+ public void onBackStarted(BackEvent backEvent) {
+ super.onBackStarted(backEvent);
+ mViewToAnimateInSwipeToDismiss = shouldAnimateContentViewInBackSwipe() ? mContent : this;
}
@Override
- public void onBackInvoked() {
- super.onBackInvoked();
- animateSlideInViewToNoScale();
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void onBackProgressed(BackEvent backEvent) {
+ final float progress = backEvent.getProgress();
+ float deceleratedProgress = Interpolators.BACK_GESTURE.getInterpolation(progress);
+ mSwipeToDismissProgress.updateValue(deceleratedProgress);
+ }
+
+ /**
+ * During predictive back swipe, the default behavior is to scale {@link AbstractSlideInView}
+ * during back swipe. This method allow subclass to scale {@link #mContent}, typically to exit
+ * search mode.
+ *
+ * <p>Note that this method can be expensive, and should only be called from
+ * {@link #onBackStarted(BackEvent)}, not from {@link #onBackProgressed(BackEvent)}.
+ */
+ protected boolean shouldAnimateContentViewInBackSwipe() {
+ return false;
+ }
+
+ protected void onUserSwipeToDismissProgressChanged() {
+ float progress = mSwipeToDismissProgress.value;
+ mIsDismissInProgress = progress > 0f;
+
+ float scale = PREDICTIVE_BACK_MIN_SCALE + (1 - PREDICTIVE_BACK_MIN_SCALE) * (1f - progress);
+ SCALE_PROPERTY.set(mViewToAnimateInSwipeToDismiss, scale);
+ setClipChildren(!mIsDismissInProgress);
+ setClipToPadding(!mIsDismissInProgress);
+ mContent.setClipChildren(!mIsDismissInProgress);
+ mContent.setClipToPadding(!mIsDismissInProgress);
+ invalidate();
}
@Override
public void onBackCancelled() {
super.onBackCancelled();
- animateSlideInViewToNoScale();
+ animateSwipeToDismissProgressToStart();
}
- protected void animateSlideInViewToNoScale() {
- mSlideInViewScale.animateToValue(1f)
- .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
- .start();
+ protected void animateSwipeToDismissProgressToStart() {
+ ObjectAnimator objectAnimator = mSwipeToDismissProgress.animateToValue(0f)
+ .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS);
+
+ // If we are animating a different view, we should reset the animating view back to
+ // AbstractSlideInView as it is the default view to animate.
+ if (this != mViewToAnimateInSwipeToDismiss) {
+ objectAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mViewToAnimateInSwipeToDismiss = AbstractSlideInView.this;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mViewToAnimateInSwipeToDismiss = AbstractSlideInView.this;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {}
+
+ @Override
+ public void onAnimationStart(Animator animator) {}
+ });
+ }
+
+ objectAnimator.start();
}
@Override
@@ -344,15 +388,14 @@
mContentBackgroundParentView.getTop() + (int) mContent.getTranslationY(),
mContentBackgroundParentView.getRight(),
mContentBackgroundParentView.getBottom()
- + (mIsBackProgressing ? getBottomOffsetPx() : 0));
+ + (mIsDismissInProgress ? getBottomOffsetPx() : 0));
mContentBackground.draw(canvas);
}
/** Return extra space revealed during predictive back animation. */
@Px
protected int getBottomOffsetPx() {
- final int height = getMeasuredHeight();
- return (int) ((height / PREDICTIVE_BACK_MIN_SCALE - height) / 2);
+ return (int) (getMeasuredHeight() * (1 - PREDICTIVE_BACK_MIN_SCALE));
}
/**
@@ -457,6 +500,10 @@
return Interpolators.ACCELERATE;
}
+ protected Interpolator getScrimInterpolator() {
+ return LINEAR;
+ }
+
protected void onCloseComplete() {
mIsOpen = false;
getPopupContainer().removeView(this);
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 31f5d65..cfac91a 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -17,12 +17,12 @@
import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
import static com.android.launcher3.Utilities.allowBGLaunch;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_KEYBOARD_CLOSED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_PENDING_INTENT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
-import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -41,6 +41,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
@@ -389,7 +390,7 @@
boolean isShortcut = (item instanceof WorkspaceItemInfo)
&& item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& !((WorkspaceItemInfo) item).isPromise();
- if (isShortcut && GO_DISABLE_WIDGETS) {
+ if (isShortcut && !WIDGETS_ENABLED) {
return null;
}
ActivityOptionsWrapper options = v != null ? getActivityLaunchOptions(v, item)
@@ -515,10 +516,21 @@
static <T extends Context & ActivityContext> T lookupContextNoThrow(Context context) {
if (context instanceof ActivityContext) {
return (T) context;
+ } else if (context instanceof ActivityContextDelegate acd) {
+ return (T) acd.mDelegate;
} else if (context instanceof ContextWrapper) {
return lookupContextNoThrow(((ContextWrapper) context).getBaseContext());
} else {
return null;
}
}
+
+ class ActivityContextDelegate extends ContextThemeWrapper {
+ public final ActivityContext mDelegate;
+
+ public ActivityContextDelegate(Context base, int themeResId, ActivityContext delegate) {
+ super(base, themeResId);
+ mDelegate = delegate;
+ }
+ }
}
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index 2f0da03..bb4f040 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -54,7 +54,7 @@
*/
public class ArrowTipView extends AbstractFloatingView {
- private static final String TAG = ArrowTipView.class.getSimpleName();
+ private static final String TAG = "ArrowTipView";
private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
private static final long SHOW_DELAY_MS = 200;
private static final long SHOW_DURATION_MS = 300;
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 5d3fa9b..f90a3e4 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -16,14 +16,14 @@
package com.android.launcher3.views;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
import static com.android.launcher3.Utilities.boundToRange;
import static com.android.launcher3.Utilities.mapToRange;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static java.lang.Math.max;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -41,6 +41,7 @@
import android.view.ViewOutlineProvider;
import androidx.annotation.Nullable;
+import androidx.core.util.Consumer;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
@@ -75,6 +76,8 @@
private final Rect mOutline = new Rect();
private final Rect mFinalDrawableBounds = new Rect();
+ @Nullable private TaskViewArtist mTaskViewArtist;
+
public ClipIconView(Context context) {
this(context, null);
}
@@ -91,10 +94,31 @@
}
/**
+ * Sets a {@link TaskViewArtist} that will draw a {@link com.android.quickstep.views.TaskView}
+ * within the clip bounds of this view.
+ */
+ public void setTaskViewArtist(TaskViewArtist taskViewArtist) {
+ if (!enableAdditionalHomeAnimations()) {
+ return;
+ }
+ mTaskViewArtist = taskViewArtist;
+ invalidate();
+ }
+
+ /**
* Update the icon UI to match the provided parameters during an animation frame
*/
public void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
boolean isOpening, View container, DeviceProfile dp) {
+ update(rect, progress, shapeProgressStart, cornerRadius, isOpening, container, dp, 255);
+ }
+
+ /**
+ * Update the icon UI to match the provided parameters during an animation frame, optionally
+ * varying the alpha of the {@link TaskViewArtist}
+ */
+ public void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
+ boolean isOpening, View container, DeviceProfile dp, int taskViewDrawAlpha) {
MarginLayoutParams lp = (MarginLayoutParams) container.getLayoutParams();
float dX = mIsRtl
@@ -108,6 +132,14 @@
float scaleX = rect.width() / minSize;
float scaleY = rect.height() / minSize;
float scale = Math.max(1f, Math.min(scaleX, scaleY));
+ if (mTaskViewArtist != null) {
+ mTaskViewArtist.taskViewDrawWidth = lp.width;
+ mTaskViewArtist.taskViewDrawHeight = lp.height;
+ mTaskViewArtist.taskViewDrawAlpha = taskViewDrawAlpha;
+ mTaskViewArtist.taskViewDrawScale = (mTaskViewArtist.drawForPortraitLayout
+ ? Math.min(lp.height, lp.width) : Math.max(lp.height, lp.width))
+ / mTaskViewArtist.taskViewMinSize;
+ }
if (Float.isNaN(scale) || Float.isInfinite(scale)) {
// Views are no longer laid out, do not update.
@@ -142,14 +174,10 @@
if (mIsAdaptiveIcon) {
if (!isOpening && progress >= shapeProgressStart) {
if (mRevealAnimator == null) {
- mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(
- this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening);
- mRevealAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mRevealAnimator = null;
- }
- });
+ mRevealAnimator = IconShape.INSTANCE.get(getContext()).getShape()
+ .createRevealAnimator(this, mStartRevealRect,
+ mOutline, mTaskCornerRadius, !isOpening);
+ mRevealAnimator.addListener(forEndCallback(() -> mRevealAnimator = null));
mRevealAnimator.start();
// We pause here so we can set the current fraction ourselves.
mRevealAnimator.pause();
@@ -230,7 +258,8 @@
mStartRevealRect.set(0, 0, originalWidth, originalHeight);
if (!isFolderIcon) {
- Utilities.scaleRectAboutCenter(mStartRevealRect, IconShape.getNormalizationScale());
+ Utilities.scaleRectAboutCenter(mStartRevealRect,
+ IconShape.INSTANCE.get(getContext()).getNormalizationScale());
}
if (dp.isLandscape) {
@@ -291,6 +320,19 @@
if (mForeground != null) {
mForeground.draw(canvas);
}
+ if (mTaskViewArtist != null) {
+ canvas.saveLayerAlpha(
+ 0,
+ 0,
+ mTaskViewArtist.taskViewDrawWidth,
+ mTaskViewArtist.taskViewDrawHeight,
+ mTaskViewArtist.taskViewDrawAlpha);
+ float drawScale = mTaskViewArtist.taskViewDrawScale;
+ canvas.translate(drawScale * mTaskViewArtist.taskViewTranslationX,
+ drawScale * mTaskViewArtist.taskViewTranslationY);
+ canvas.scale(drawScale, drawScale);
+ mTaskViewArtist.taskViewDrawCallback.accept(canvas);
+ }
canvas.restoreToCount(count);
}
@@ -307,5 +349,37 @@
mRevealAnimator = null;
mTaskCornerRadius = 0;
mOutline.setEmpty();
+ mTaskViewArtist = null;
+ }
+
+ /**
+ * Utility class to help draw a {@link com.android.quickstep.views.TaskView} within
+ * a {@link ClipIconView} bounds.
+ */
+ public static class TaskViewArtist {
+
+ public final Consumer<Canvas> taskViewDrawCallback;
+ public final float taskViewTranslationX;
+ public final float taskViewTranslationY;
+ public final float taskViewMinSize;
+ public final boolean drawForPortraitLayout;
+
+ public int taskViewDrawAlpha;
+ public float taskViewDrawScale;
+ public int taskViewDrawWidth;
+ public int taskViewDrawHeight;
+
+ public TaskViewArtist(
+ Consumer<Canvas> taskViewDrawCallback,
+ float taskViewTranslationX,
+ float taskViewTranslationY,
+ float taskViewMinSize,
+ boolean drawForPortraitLayout) {
+ this.taskViewDrawCallback = taskViewDrawCallback;
+ this.taskViewTranslationX = taskViewTranslationX;
+ this.taskViewTranslationY = taskViewTranslationY;
+ this.taskViewMinSize = taskViewMinSize;
+ this.drawForPortraitLayout = drawForPortraitLayout;
+ }
}
}
diff --git a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
index a309e6e..bc66a33 100644
--- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
+++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
@@ -77,6 +77,7 @@
canvas.restore();
drawDotIfNecessary(canvas);
+ drawRunningAppIndicatorIfNecessary(canvas);
}
public static class ShadowInfo {
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index f76b53b..1e577be 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -18,6 +18,7 @@
import static android.view.Gravity.LEFT;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
import static com.android.launcher3.Utilities.getFullDrawable;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -68,7 +69,7 @@
public class FloatingIconView extends FrameLayout implements
Animator.AnimatorListener, OnGlobalLayoutListener, FloatingView {
- private static final String TAG = FloatingIconView.class.getSimpleName();
+ private static final String TAG = "FloatingIconView";
// Manages loading the icon on a worker thread
private static @Nullable IconLoadResult sIconLoadResult;
@@ -145,18 +146,33 @@
}
/**
- * Positions this view to match the size and location of {@param rect}.
- * @param alpha The alpha[0, 1] of the entire floating view.
- * @param progress A value from [0, 1] that represents the animation progress.
- * @param shapeProgressStart The progress value at which to start the shape reveal.
- * @param cornerRadius The corner radius of {@param rect}.
- * @param isOpening True if view is used for app open animation, false for app close animation.
+ * Positions this view to match the size and location of {@code rect}.
*/
public void update(float alpha, RectF rect, float progress, float shapeProgressStart,
float cornerRadius, boolean isOpening) {
- setAlpha(alpha);
+ update(alpha, rect, progress, shapeProgressStart, cornerRadius, isOpening, 0);
+ }
+
+ /**
+ * Positions this view to match the size and location of {@code rect}.
+ * <p>
+ * @param alpha The alpha[0, 1] of the entire floating view.
+ * @param progress A value from [0, 1] that represents the animation progress.
+ * @param shapeProgressStart The progress value at which to start the shape reveal.
+ * @param cornerRadius The corner radius of {@code rect}.
+ * @param isOpening True if view is used for app open animation, false for app close animation.
+ * @param taskViewDrawAlpha the drawn {@link com.android.quickstep.views.TaskView} alpha
+ */
+ public void update(float alpha, RectF rect, float progress, float shapeProgressStart,
+ float cornerRadius, boolean isOpening, int taskViewDrawAlpha) {
+ // The non-running task home animation has some very funky first few frames because this
+ // FIV hasn't fully laid out. During those frames, hide this FIV and continue drawing the
+ // TaskView directly while transforming it in the place of this FIV. However, if we fade
+ // the TaskView at all, we need to display this FIV regardless.
+ setAlpha(!enableAdditionalHomeAnimations() || isLaidOut() || taskViewDrawAlpha < 255
+ ? alpha : 0f);
mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, this,
- mLauncher.getDeviceProfile());
+ mLauncher.getDeviceProfile(), taskViewDrawAlpha);
if (mFadeOutView != null) {
// The alpha goes from 1 to 0 when progress is 0 and 0.33 respectively.
@@ -164,6 +180,14 @@
}
}
+ /**
+ * Sets a {@link com.android.quickstep.views.TaskView} that will draw a
+ * {@link com.android.quickstep.views.TaskView} within the {@code mClipIconView} clip bounds
+ */
+ public void setOverlayArtist(ClipIconView.TaskViewArtist taskViewArtist) {
+ mClipIconView.setTaskViewArtist(taskViewArtist);
+ }
+
@Override
public void onAnimationEnd(Animator animator) {
if (mLoadIconSignal != null) {
@@ -178,8 +202,8 @@
}
/**
- * Sets the size and position of this view to match {@param v}.
- *
+ * Sets the size and position of this view to match {@code v}.
+ * <p>
* @param v The view to copy
* @param positionOut Rect that will hold the size and position of v.
*/
@@ -253,10 +277,11 @@
/**
* Loads the icon and saves the results to {@link #sIconLoadResult}.
+ * <p>
* Runs onIconLoaded callback (if any), which signifies that the FloatingIconView is
* ready to display the icon. Otherwise, the FloatingIconView will grab the results when its
* initialized.
- *
+ * <p>
* @param originalView The View that the FloatingIconView will replace.
* @param info ItemInfo of the originalView
* @param pos The position of the view.
@@ -282,6 +307,8 @@
} else if (btvIcon instanceof PreloadIconDrawable) {
// Force the progress bar to display.
drawable = btvIcon;
+ } else if (originalView instanceof ImageView) {
+ drawable = ((ImageView) originalView).getDrawable();
} else {
int width = (int) pos.width();
int height = (int) pos.height();
@@ -321,8 +348,8 @@
}
/**
- * Sets the drawables of the {@param originalView} onto this view.
- *
+ * Sets the drawables of the {@code originalView} onto this view.
+ * <p>
* @param drawable The drawable of the original view.
* @param badge The badge of the original view.
* @param iconOffset The amount of offset needed to match this view with the original view.
@@ -365,11 +392,11 @@
/**
* Draws the drawable of the BubbleTextView behind ClipIconView
- *
+ * <p>
* This is used to:
* - Have icon displayed while Adaptive Icon is loading
* - Displays the built in shadow to ensure a clean handoff
- *
+ * <p>
* Allows nullable as this may be cleared when drawing is deferred to ClipIconView.
*/
private void setOriginalDrawableBackground(@Nullable Supplier<Drawable> btvIcon) {
@@ -570,11 +597,12 @@
}
/**
- * Creates a floating icon view for {@param originalView}.
+ * Creates a floating icon view for {@code originalView}.
+ * <p>
* @param originalView The view to copy
* @param visibilitySyncView A view whose visibility should update in sync with originalView.
* @param fadeOutView A view that will fade out as the animation progresses.
- * @param hideOriginal If true, it will hide {@param originalView} while this view is visible.
+ * @param hideOriginal If true, it will hide {@code originalView} while this view is visible.
* Else, we will not draw anything in this view.
* @param positionOut Rect that will hold the size and position of v.
* @param isOpening True if this view replaces the icon for app open animation.
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index c60e1a4..cab7982 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.views;
+import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView;
import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
@@ -159,7 +160,7 @@
if (mContract == null) {
return;
}
- View icon = mLauncher.getFirstMatchForAppClose(-1,
+ View icon = mLauncher.getFirstMatchForAppClose(NO_MATCHING_ID,
mContract.componentName.getPackageName(), mContract.user,
false /* supportsAllAppsState */);
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 5cdad03..62eed5c 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -17,6 +17,7 @@
import static androidx.core.content.ContextCompat.getColorStateList;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
@@ -47,7 +48,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager.EventEnum;
-import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -207,7 +207,7 @@
R.drawable.ic_palette,
IGNORE,
OptionsPopupView::startWallpaperPicker));
- if (!WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (WIDGETS_ENABLED) {
options.add(new OptionItem(launcher,
R.string.widget_button_text,
R.drawable.ic_widget,
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 8408cc7..fa17b7b 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -30,6 +30,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
@@ -108,6 +109,13 @@
private float mLastTouchY;
private boolean mIsDragging;
+ /**
+ * Tracks whether a keyboard hide request has been sent due to downward scrolling.
+ * <p>
+ * Set to true when scrolling down and reset when scrolling up to prevents redundant hide
+ * requests during continuous downward scrolls.
+ */
+ private boolean mRequestedHideKeyboard;
private boolean mIsThumbDetached;
private final boolean mCanThumbDetach;
private boolean mIgnoreDragGesture;
@@ -121,11 +129,12 @@
// Fast scroller popup
private TextView mPopupView;
private boolean mPopupVisible;
- private String mPopupSectionName;
+ private CharSequence mPopupSectionName;
private Insets mSystemGestureInsets;
protected FastScrollRecyclerView mRv;
private RecyclerView.OnScrollListener mOnScrollListener;
+ private final ActivityContext mActivityContext;
private int mDownX;
private int mDownY;
@@ -162,7 +171,7 @@
mDeltaThreshold = res.getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
mScrollbarLeftOffsetTouchDelegate = res.getDisplayMetrics().density
* SCROLLBAR_LEFT_OFFSET_TOUCH_DELEGATE_DP;
-
+ mActivityContext = ActivityContext.lookupContext(context);
TypedArray ta =
context.obtainStyledAttributes(attrs, R.styleable.RecyclerViewFastScroller, defStyleAttr, 0);
mCanThumbDetach = ta.getBoolean(R.styleable.RecyclerViewFastScroller_canThumbDetach, false);
@@ -247,6 +256,7 @@
mDownX = x;
mDownY = mLastY = y;
mDownTimeStampMillis = ev.getDownTime();
+ mRequestedHideKeyboard = false;
if ((Math.abs(mDy) < mDeltaThreshold &&
mRv.getScrollState() != SCROLL_STATE_IDLE)) {
@@ -259,6 +269,7 @@
}
break;
case MotionEvent.ACTION_MOVE:
+ boolean isScrollingDown = y > mLastY;
mLastY = y;
int absDeltaY = Math.abs(y - mDownY);
int absDeltaX = Math.abs(x - mDownX);
@@ -274,6 +285,14 @@
}
}
if (mIsDragging) {
+ if (isScrollingDown) {
+ if (!mRequestedHideKeyboard) {
+ mActivityContext.hideKeyboard();
+ }
+ mRequestedHideKeyboard = true;
+ } else {
+ mRequestedHideKeyboard = false;
+ }
updateFastScrollSectionNameAndThumbOffset(y);
}
break;
@@ -293,7 +312,6 @@
}
private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
- ActivityContext.lookupContext(getContext()).hideKeyboard();
mIsDragging = true;
if (mCanThumbDetach) {
mIsThumbDetached = true;
@@ -307,13 +325,13 @@
// Update the fastscroller section name at this touch position
int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
- String sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
+ CharSequence sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
if (!sectionName.equals(mPopupSectionName)) {
mPopupSectionName = sectionName;
mPopupView.setText(sectionName);
performHapticFeedback(CLOCK_TICK);
}
- animatePopupVisibility(!sectionName.isEmpty());
+ animatePopupVisibility(!TextUtils.isEmpty(sectionName));
mLastTouchY = boundedY;
setThumbOffsetY((int) mLastTouchY);
}
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index ca80c51..f6c4984 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -26,6 +26,7 @@
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.annotation.Px;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.BaseActivity;
@@ -187,9 +188,19 @@
* A Utility interface allowing for other surfaces to draw on ScrimView
*/
public interface ScrimDrawingController {
- /**
- * Called inside ScrimView#OnDraw
- */
- void drawOnScrimWithScale(Canvas canvas, float scale);
+
+ /** Draw scrim view on canvas with scale. */
+ default void drawOnScrimWithScale(Canvas canvas, float scale) {
+ drawOnScrimWithScaleAndBottomOffset(canvas, scale, 0);
+ }
+
+ /** Draw scrim view on canvas with bottomOffset. */
+ default void drawOnScrimWithBottomOffset(Canvas canvas, @Px int bottomOffsetPx) {
+ drawOnScrimWithScaleAndBottomOffset(canvas, 1f, bottomOffsetPx);
+ }
+
+ /** Draw scrim view on canvas with scale and bottomOffset. */
+ void drawOnScrimWithScaleAndBottomOffset(
+ Canvas canvas, float scale, @Px int bottomOffsetPx);
}
}
diff --git a/src/com/android/launcher3/views/StickyHeaderLayout.java b/src/com/android/launcher3/views/StickyHeaderLayout.java
index d6481a9..090251f 100644
--- a/src/com/android/launcher3/views/StickyHeaderLayout.java
+++ b/src/com/android/launcher3/views/StickyHeaderLayout.java
@@ -36,6 +36,9 @@
import com.android.launcher3.R;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A {@link LinearLayout} container which allows scrolling parts of its content based on the
* scroll of a different view. Views which are marked as sticky are not scrolled, giving the
@@ -242,6 +245,22 @@
return p instanceof MyLayoutParams;
}
+ /**
+ * Return a list of all the children that have the sticky layout param set.
+ */
+ public List<View> getStickyChildren() {
+ List<View> stickyChildren = new ArrayList<>();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View v = getChildAt(i);
+ MyLayoutParams lp = (MyLayoutParams) v.getLayoutParams();
+ if (lp.sticky) {
+ stickyChildren.add(v);
+ }
+ }
+ return stickyChildren;
+ }
+
private static class MyLayoutParams extends LayoutParams {
public final boolean sticky;
diff --git a/src/com/android/launcher3/views/WidgetsEduView.java b/src/com/android/launcher3/views/WidgetsEduView.java
deleted file mode 100644
index 40c6115..0000000
--- a/src/com/android/launcher3/views/WidgetsEduView.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2021 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.views;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.R;
-
-/**
- * Education view about widgets.
- */
-public class WidgetsEduView extends AbstractSlideInView<BaseActivity> implements Insettable {
-
- private static final int DEFAULT_CLOSE_DURATION = 200;
-
- private Rect mInsets = new Rect();
-
- public WidgetsEduView(Context context, AttributeSet attr) {
- this(context, attr, 0);
- }
-
- public WidgetsEduView(Context context, AttributeSet attrs,
- int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- @Override
- protected void handleClose(boolean animate) {
- handleClose(true, DEFAULT_CLOSE_DURATION);
- }
-
- @Override
- protected boolean isOfType(int type) {
- return (type & TYPE_WIDGETS_EDUCATION_DIALOG) != 0;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mContent = findViewById(R.id.edu_view);
- findViewById(R.id.edu_close_button)
- .setOnClickListener(v -> close(/* animate= */ true));
- }
-
- @Override
- public void setInsets(Rect insets) {
- mInsets.set(insets);
- mContent.setPadding(mContent.getPaddingStart(),
- mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom);
- }
-
- private void show() {
- attachToContainer();
- animateOpen();
- }
-
- @Override
- protected int getScrimColor(Context context) {
- return context.getResources().getColor(R.color.widgets_picker_scrim);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int width = r - l;
- int height = b - t;
-
- // Lay out the content as center bottom aligned.
- int contentWidth = mContent.getMeasuredWidth();
- int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
- mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
- contentLeft + contentWidth, height);
-
- setTranslationShift(mTranslationShift);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
- int widthUsed;
- if (mInsets.bottom > 0) {
- // Extra space between this view and mContent horizontally when the sheet is shown in
- // portrait mode.
- widthUsed = mInsets.left + mInsets.right;
- } else {
- // Extra space between this view and mContent horizontally when the sheet is shown in
- // landscape mode.
- Rect padding = deviceProfile.workspacePadding;
- widthUsed = Math.max(padding.left + padding.right,
- 2 * (mInsets.left + mInsets.right));
- }
-
- int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
- measureChildWithMargins(mContent, widthMeasureSpec,
- widthUsed, heightMeasureSpec, heightUsed);
- setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
- MeasureSpec.getSize(heightMeasureSpec));
- }
-
- private void animateOpen() {
- if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
- return;
- }
- mIsOpen = true;
- setUpDefaultOpenAnimation().start();
- }
-
- /** Shows widget education dialog. */
- public static WidgetsEduView showEducationDialog(BaseActivity activity) {
- LayoutInflater layoutInflater = LayoutInflater.from(activity);
- WidgetsEduView v = (WidgetsEduView) layoutInflater.inflate(
- R.layout.widgets_edu, activity.getDragLayer(), false);
- v.show();
- return v;
- }
-}
diff --git a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
index 4f5d311..5e702aa 100644
--- a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
@@ -145,7 +145,10 @@
@Override
protected int getScrimColor(Context context) {
- return context.getResources().getColor(R.color.widgets_picker_scrim);
+ // Don't add a scrim when using the standalone picker activity. The background dimming is
+ // handled by applying dimBackground in the activity theme, so the scrim doesn't slide in
+ // with the window.
+ return -1;
}
@SuppressLint("NewApi") // Already added API check.
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 54ce973..1368084 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -16,33 +16,35 @@
package com.android.launcher3.widget;
import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
-import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
-import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_TIP_SEEN;
+import static com.android.launcher3.Flags.enableWidgetTapToAdd;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_ADD_BUTTON_TAP;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.WindowInsets;
import android.view.animation.Interpolator;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
-import androidx.core.view.ViewCompat;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -50,7 +52,8 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.AbstractSlideInView;
-import com.android.launcher3.views.ArrowTipView;
+
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Base class for various widgets popup
@@ -73,6 +76,9 @@
private boolean mDisableNavBarScrim = false;
+ @Nullable private WidgetCell mWidgetCellWithAddButton = null;
+ @Nullable private WidgetItem mLastSelectedWidgetItem = null;
+
public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContentHorizontalMargin = getWidgetListHorizontalMargin();
@@ -123,14 +129,143 @@
@Override
public final void onClick(View v) {
- if (v instanceof WidgetCell) {
- mActivityContext.getItemOnClickListener().onClick(v);
- } else if (v.getParent() instanceof WidgetCell wc) {
+ WidgetCell wc;
+ if (v instanceof WidgetCell view) {
+ wc = view;
+ } else if (v.getParent() instanceof WidgetCell parent) {
+ wc = parent;
+ } else {
+ return;
+ }
+
+ if (enableWidgetTapToAdd()) {
+ scrollToWidgetCell(wc);
+
+ if (mWidgetCellWithAddButton != null) {
+ if (mWidgetCellWithAddButton.isShowingAddButton()) {
+ // If there is a add button currently showing, hide it.
+ mWidgetCellWithAddButton.hideAddButton(/* animate= */ true);
+ } else {
+ // The last recorded widget cell to show an add button is no longer showing it,
+ // likely because the widget cell has been recycled or lost focus. If this is
+ // the cell that has been clicked, we will show it below.
+ mWidgetCellWithAddButton = null;
+ }
+ }
+
+ if (mWidgetCellWithAddButton != wc) {
+ // If click is on a cell not showing an add button, show it now.
+ final PendingAddItemInfo info = (PendingAddItemInfo) wc.getTag();
+ if (mActivityContext instanceof Launcher) {
+ wc.showAddButton((view) -> addWidget(info));
+ } else {
+ wc.showAddButton((view) -> mActivityContext.getItemOnClickListener()
+ .onClick(wc));
+ }
+ }
+
+ mWidgetCellWithAddButton = mWidgetCellWithAddButton != wc ? wc : null;
+ if (mWidgetCellWithAddButton != null) {
+ mLastSelectedWidgetItem = mWidgetCellWithAddButton.getWidgetItem();
+ } else {
+ mLastSelectedWidgetItem = null;
+ }
+ } else {
mActivityContext.getItemOnClickListener().onClick(wc);
}
}
@Override
+ protected float getShiftRange() {
+ // We add the extra height added during predictive back / swipe up to the shift range, so
+ // that the idle interpolator knows to animate the view off fully.
+ return mContent.getHeight() + getBottomOffsetPx();
+ }
+
+ /**
+ * Click handler for tap to add button. This handler assumes we are in the Launcher activity and
+ * should not be used when the widget sheet is displayed elsewhere.
+ */
+ private void addWidget(@NonNull PendingAddItemInfo info) {
+ // Using a boolean flag here to make sure the callback is only run once. This should never
+ // happen because we close the sheet and it will be reconstructed the next time it is
+ // needed.
+ final AtomicBoolean hasRun = new AtomicBoolean(false);
+ addOnCloseListener(() -> {
+ if (hasRun.get()) return;
+ hasRun.set(true);
+
+ // Going to NORMAL state will also dismiss the All Apps view if it is showing.
+ Launcher launcher = Launcher.getLauncher(mActivityContext);
+ launcher.getStateManager().goToState(NORMAL, forSuccessCallback(() -> {
+ launcher.getAccessibilityDelegate().addToWorkspace(info,
+ /*accessibility=*/ false,
+ /*finishCallback=*/ (success) -> {
+ mActivityContext.getStatsLogManager()
+ .logger()
+ .withItemInfo(info)
+ .log(LAUNCHER_WIDGET_ADD_BUTTON_TAP);
+ });
+ }));
+ });
+ close(/* animate= */ true);
+ }
+
+ /**
+ * Scroll to show the widget cell. If both the bottom and top of the cell are clipped, this will
+ * prioritize showing the bottom of the cell (where the add button is).
+ */
+ private void scrollToWidgetCell(@NonNull WidgetCell wc) {
+ final int headerTopClip = getHeaderTopClip(wc);
+ final Rect visibleRect = new Rect();
+ final boolean isPartiallyVisible = wc.getLocalVisibleRect(visibleRect);
+ int scrollByY = 0;
+ if (isPartiallyVisible) {
+ final int scrollPadding = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_button_scroll_padding);
+ final int topClip = visibleRect.top + headerTopClip;
+ final int bottomClip = wc.getHeight() - visibleRect.bottom;
+ if (bottomClip != 0) {
+ scrollByY = bottomClip + scrollPadding;
+ } else if (topClip != 0) {
+ scrollByY = -topClip - scrollPadding;
+ }
+ }
+
+ if (isPartiallyVisible && scrollByY == 0) {
+ // Widget is fully visible.
+ return;
+ } else if (!isPartiallyVisible) {
+ Log.e("BaseWidgetSheet", "click on invisible WidgetCell should not be possible");
+ return;
+ }
+
+ scrollCellContainerByY(wc, scrollByY);
+ }
+
+ /**
+ * Find the nearest scrollable container of the given WidgetCell, and scroll by the given
+ * amount.
+ */
+ protected abstract void scrollCellContainerByY(WidgetCell wc, int scrollByY);
+
+
+ /**
+ * Return the top clip of any sticky headers over the given cell.
+ */
+ protected int getHeaderTopClip(@NonNull WidgetCell cell) {
+ return 0;
+ }
+
+ /**
+ * Returns the component of the widget that is currently showing an add button, if any.
+ */
+ @Nullable
+ protected WidgetItem getLastSelectedWidgetItem() {
+ return mLastSelectedWidgetItem;
+ }
+
+ @Override
public boolean onLongClick(View v) {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
v.cancelLongPress();
@@ -215,19 +350,8 @@
MeasureSpec.getSize(heightMeasureSpec));
}
- private int getTabletHorizontalMargin(DeviceProfile deviceProfile) {
- // All bottom-sheets showing widgets will be full-width across all devices.
- if (enableCategorizedWidgetSuggestions()) {
- return 0;
- }
- if (deviceProfile.isLandscape && !deviceProfile.isTwoPanels) {
- return getResources().getDimensionPixelSize(
- R.dimen.widget_picker_landscape_tablet_left_right_margin);
- }
- if (deviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {
- return getResources().getDimensionPixelSize(
- R.dimen.widget_picker_two_panels_left_right_margin);
- }
+ /** Returns the horizontal margins to be applied to the widget sheet. **/
+ protected int getTabletHorizontalMargin(DeviceProfile deviceProfile) {
return deviceProfile.allAppsLeftRightMargin;
}
@@ -266,31 +390,6 @@
return mActivityContext.getSystemUiController();
}
- /** Shows education tip on top center of {@code view} if view is laid out. */
- @Nullable
- protected ArrowTipView showEducationTipOnViewIfPossible(@Nullable View view) {
- if (view == null || !ViewCompat.isLaidOut(view)) {
- return null;
- }
- int[] coords = new int[2];
- view.getLocationOnScreen(coords);
- ArrowTipView arrowTipView =
- new ArrowTipView(mActivityContext, /* isPointingUp= */ false).showAtLocation(
- getContext().getString(R.string.long_press_widget_to_add),
- /* arrowXCoord= */coords[0] + view.getWidth() / 2,
- /* yCoord= */coords[1]);
- if (arrowTipView != null) {
- LauncherPrefs.get(getContext()).put(WIDGETS_EDUCATION_TIP_SEEN, true);
- }
- return arrowTipView;
- }
-
- /** Returns {@code true} if tip has previously been shown on any of {@link BaseWidgetSheet}. */
- protected boolean hasSeenEducationTip() {
- return LauncherPrefs.get(getContext()).get(WIDGETS_EDUCATION_TIP_SEEN)
- || Utilities.isRunningInTestHarness();
- }
-
@Override
protected void setTranslationShift(float translationShift) {
super.setTranslationShift(translationShift);
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index aab78bd..2817299 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -27,12 +27,12 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
import android.util.Size;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
@@ -45,6 +45,7 @@
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.util.WidgetSizes;
@@ -68,8 +69,7 @@
}
/**
- * Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be
- * called on UI thread.
+ * Generates the widget preview on {@link Executors#UI_HELPER_EXECUTOR}.
*
* @return a request id which can be used to cancel the request.
*/
@@ -78,7 +78,7 @@
@NonNull WidgetItem item,
@NonNull Size previewSize,
@NonNull Consumer<Bitmap> callback) {
- Handler handler = Executors.UI_HELPER_EXECUTOR.getHandler();
+ Handler handler = getLoaderExecutor().getHandler();
CancellableTask<Bitmap> request = new CancellableTask<>(
() -> generatePreview(item, previewSize.getWidth(), previewSize.getHeight()),
MAIN_EXECUTOR,
@@ -87,6 +87,12 @@
return request;
}
+ @VisibleForTesting
+ @NonNull
+ public static LooperExecutor getLoaderExecutor() {
+ return Executors.UI_HELPER_EXECUTOR;
+ }
+
/**
* Returns a generated preview for a widget and if the preview should be saved in persistent
* storage.
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 2259e3c..44ab966 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -42,7 +42,6 @@
import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -52,8 +51,7 @@
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
- implements TouchCompleteListener, View.OnLongClickListener,
- LocalColorExtractor.Listener {
+ implements TouchCompleteListener, View.OnLongClickListener {
private static final String TAG = "LauncherAppWidgetHostView";
@@ -70,13 +68,9 @@
private static final String TRACE_METHOD_NAME = "appwidget load-widget ";
- private final Rect mTempRect = new Rect();
private final CheckLongPressHelper mLongPressHelper;
protected final ActivityContext mActivityContext;
- // Maintain the color manager.
- private final LocalColorExtractor mColorExtractor;
-
private boolean mIsScrollable;
private boolean mIsAttachedToWindow;
private boolean mIsAutoAdvanceRegistered;
@@ -84,14 +78,11 @@
private long mDeferUpdatesUntilMillis = 0;
RemoteViews mLastRemoteViews;
- private boolean mHasDeferredColorChange = false;
- private @Nullable SparseIntArray mDeferredColorChange = null;
-
- // The following member variables are only used during drag-n-drop.
- private boolean mIsInDragMode = false;
private boolean mTrackingWidgetUpdate = false;
+ private int mFocusRectOutsets = 0;
+
public LauncherAppWidgetHostView(Context context) {
super(context);
mActivityContext = ActivityContext.lookupContext(context);
@@ -100,12 +91,13 @@
setBackgroundResource(R.drawable.widget_internal_focus_bg);
if (Flags.enableFocusOutline()) {
setDefaultFocusHighlightEnabled(false);
+ mFocusRectOutsets = context.getResources().getDimensionPixelSize(
+ R.dimen.focus_rect_widget_outsets);
}
if (Themes.getAttrBoolean(context, R.attr.isWorkspaceDarkText)) {
setOnLightBackground(true);
}
- mColorExtractor = new LocalColorExtractor(); // no-op
}
@Override
@@ -173,8 +165,8 @@
}
/**
- * Returns true if the application of {@link RemoteViews} through {@link #updateAppWidget} and
- * colors through {@link #onColorsChanged} are currently being deferred.
+ * Returns true if the application of {@link RemoteViews} through {@link #updateAppWidget} are
+ * currently being deferred.
* @see #beginDeferringUpdates()
*/
private boolean isDeferringUpdates() {
@@ -183,9 +175,8 @@
/**
* Begin deferring the application of any {@link RemoteViews} updates made through
- * {@link #updateAppWidget} and color changes through {@link #onColorsChanged} until
- * {@link #endDeferringUpdates()} has been called or the next {@link #updateAppWidget} or
- * {@link #onColorsChanged} call after {@link #UPDATE_LOCK_TIMEOUT_MILLIS} have elapsed.
+ * {@link #updateAppWidget} until {@link #endDeferringUpdates()} has been called or the next
+ * {@link #updateAppWidget} call after {@link #UPDATE_LOCK_TIMEOUT_MILLIS} have elapsed.
*/
public void beginDeferringUpdates() {
mDeferUpdatesUntilMillis = SystemClock.uptimeMillis() + UPDATE_LOCK_TIMEOUT_MILLIS;
@@ -193,26 +184,16 @@
/**
* Stop deferring the application of {@link RemoteViews} updates made through
- * {@link #updateAppWidget} and color changes made through {@link #onColorsChanged} and apply
- * any deferred updates.
+ * {@link #updateAppWidget} and apply any deferred updates.
*/
public void endDeferringUpdates() {
RemoteViews remoteViews;
- SparseIntArray deferredColors;
- boolean hasDeferredColors;
mDeferUpdatesUntilMillis = 0;
remoteViews = mLastRemoteViews;
- deferredColors = mDeferredColorChange;
- hasDeferredColors = mHasDeferredColorChange;
- mDeferredColorChange = null;
- mHasDeferredColorChange = false;
if (remoteViews != null) {
updateAppWidget(remoteViews);
}
- if (hasDeferredColors) {
- onColorsChanged(deferredColors);
- }
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -238,7 +219,6 @@
super.onAttachedToWindow();
mIsAttachedToWindow = true;
checkIfAutoAdvance();
- mColorExtractor.setListener(this);
}
@Override
@@ -249,7 +229,6 @@
// state is updated. So isAttachedToWindow() will return true until next frame.
mIsAttachedToWindow = false;
checkIfAutoAdvance();
- mColorExtractor.setListener(null);
}
@Override
@@ -269,6 +248,13 @@
}
@Override
+ public void getFocusedRect(Rect r) {
+ super.getFocusedRect(r);
+ // Outset to a larger rect for drawing a padding between focus outline and widget
+ r.inset(mFocusRectOutsets, mFocusRectOutsets);
+ }
+
+ @Override
public void onTouchComplete() {
if (!mLongPressHelper.hasPerformedLongPress()) {
// If a long press has been performed, we don't want to clear the record of that since
@@ -281,29 +267,6 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mIsScrollable = checkScrollableRecursively(this);
-
- if (!mIsInDragMode && getTag() instanceof LauncherAppWidgetInfo info) {
- mTempRect.set(left, top, right, bottom);
- mColorExtractor.setWorkspaceLocation(mTempRect, (View) getParent(), info.screenId);
- }
- }
-
- /** Starts the drag mode. */
- public void startDrag() {
- mIsInDragMode = true;
- }
-
- /** Handles a drag event occurred on a workspace page corresponding to the {@code screenId}. */
- public void handleDrag(Rect rectInView, View view, int screenId) {
- if (mIsInDragMode) {
- mColorExtractor.setWorkspaceLocation(rectInView, view, screenId);
- }
- }
-
- /** Ends the drag mode. */
- public void endDrag() {
- mIsInDragMode = false;
- requestLayout();
}
/**
@@ -327,20 +290,6 @@
}
@Override
- public void onColorsChanged(SparseIntArray colors) {
- if (isDeferringUpdates()) {
- mDeferredColorChange = colors;
- mHasDeferredColorChange = true;
- return;
- }
- mDeferredColorChange = null;
- mHasDeferredColorChange = false;
-
- // setColorResources will reapply the view, which must happen in the UI thread.
- post(() -> setColorResources(colors));
- }
-
- @Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(getClass().getName());
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index 15bd6ed..a5e22c5 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -17,6 +17,7 @@
import static android.app.Activity.RESULT_CANCELED;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.Flags.enableWorkspaceInflation;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo;
@@ -40,7 +41,6 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -100,10 +100,10 @@
* Starts listening to the widget updates from the server side
*/
public void startListening() {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
return;
}
- setListeningFlag(true);
+
try {
mWidgetHost.startListening();
} catch (Exception e) {
@@ -115,6 +115,8 @@
// have been established by this point, and we will end up populating the
// widgets upon bind anyway. See issue 14255011 for more context.
}
+ // TODO: Investigate why widgetHost.startListening() always return non-empty updates
+ setListeningFlag(true);
updateDeferredView();
}
@@ -175,7 +177,7 @@
* @return The allocated app widget id if allocation is successful, returns -1 otherwise
*/
public int allocateAppWidgetId() {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
return AppWidgetManager.INVALID_APPWIDGET_ID;
}
@@ -208,7 +210,7 @@
*/
public void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId,
int requestCode) {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
sendActionCancelled(activity, requestCode);
return;
}
@@ -259,7 +261,7 @@
*/
public void startBindFlow(@NonNull BaseActivity activity,
int appWidgetId, @NonNull AppWidgetProviderInfo info, int requestCode) {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
sendActionCancelled(activity, requestCode);
return;
}
@@ -277,7 +279,7 @@
* Stop the host from listening to the widget updates
*/
public void stopListening() {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
return;
}
mWidgetHost.stopListening();
@@ -347,7 +349,13 @@
@NonNull
public final AppWidgetHostView attachViewToHostAndGetAttachedView(
@NonNull LauncherAppWidgetHostView view) {
- if (mViews.get(view.getAppWidgetId()) != view) {
+
+ // Binder can also inflate placeholder widgets in case of backup-restore. Skip
+ // attaching such widgets
+ boolean isRealWidget = ((view instanceof PendingAppWidgetHostView pw)
+ ? pw.isDeferredWidget() : true)
+ && view.getAppWidgetInfo() != null;
+ if (isRealWidget && mViews.get(view.getAppWidgetId()) != view) {
view = recycleExistingView(view);
mViews.put(view.getAppWidgetId(), view);
}
@@ -442,6 +450,13 @@
}
/**
+ * Clears all the internal widget views
+ */
+ public void clearWidgetViews() {
+ clearViews();
+ }
+
+ /**
* @return True if the host is listening to the updates, false otherwise
*/
public boolean isListening() {
diff --git a/src/com/android/launcher3/widget/LocalColorExtractor.java b/src/com/android/launcher3/widget/LocalColorExtractor.java
index 96e7531..7b500c7 100644
--- a/src/com/android/launcher3/widget/LocalColorExtractor.java
+++ b/src/com/android/launcher3/widget/LocalColorExtractor.java
@@ -17,11 +17,8 @@
package com.android.launcher3.widget;
import android.app.WallpaperColors;
-import android.appwidget.AppWidgetHostView;
import android.content.Context;
-import android.graphics.Rect;
import android.util.SparseIntArray;
-import android.view.View;
import androidx.annotation.Nullable;
@@ -31,18 +28,6 @@
/** Extracts the colors we need from the wallpaper at given locations. */
public class LocalColorExtractor implements ResourceBasedOverride {
- /** Listener for color changes on a screen location. */
- public interface Listener {
- /**
- * Method called when the colors on a registered location has changed.
- *
- * {@code extractedColors} maps the color resources {@code android.R.colors.system_*} to
- * their value, in a format that can be passed directly to
- * {@link AppWidgetHostView#setColorResources(SparseIntArray)}.
- */
- void onColorsChanged(SparseIntArray extractedColors);
- }
-
/**
* Creates a new instance of LocalColorExtractor
*/
@@ -51,19 +36,6 @@
R.string.local_colors_extraction_class);
}
- /** Sets the object that will receive the color changes. */
- public void setListener(@Nullable Listener listener) {
- // no-op
- }
-
- /**
- * Sets the location used for color extraction
- * @param pos position to use for color extraction
- * @param child view whose coordinate space is used for {@code pos}
- * @param screenId the workspace screenId
- */
- public void setWorkspaceLocation(Rect pos, View child, int screenId) { }
-
/**
* Updates the base context to contain the colors override
*/
@@ -76,5 +48,4 @@
public SparseIntArray generateColorsOverride(WallpaperColors colors) {
return null;
}
-
}
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index a501960..a916252 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -25,7 +25,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.widget.picker.WidgetRecommendationCategory;
import com.android.launcher3.widget.util.WidgetSizes;
@@ -82,8 +82,8 @@
@NonNull
@Override
- public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo folderInfo) {
- LauncherAtom.ItemInfo info = super.buildProto(folderInfo);
+ public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo collectionInfo) {
+ LauncherAtom.ItemInfo info = super.buildProto(collectionInfo);
return info.toBuilder()
.addItemAttributes(LauncherAppWidgetInfo.getAttribute(sourceContainer))
.build();
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 86400ba..9c9b80d 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -29,9 +29,11 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -76,6 +78,10 @@
private final Rect mRect = new Rect();
+ private final Matrix mMatrix = new Matrix();
+ private final RectF mPreviewBitmapRect = new RectF();
+ private final RectF mCanvasRect = new RectF();
+
private final LauncherWidgetHolder mWidgetHolder;
private final LauncherAppWidgetProviderInfo mAppwidget;
private final LauncherAppWidgetInfo mInfo;
@@ -103,9 +109,14 @@
public PendingAppWidgetHostView(Context context, LauncherWidgetHolder widgetHolder,
LauncherAppWidgetInfo info, @Nullable LauncherAppWidgetProviderInfo appWidget) {
- this(context, widgetHolder, info, appWidget,
- context.getResources().getText(R.string.gadget_complete_setup_text));
+ this(context, widgetHolder, info, appWidget, null);
+ }
+ public PendingAppWidgetHostView(Context context, LauncherWidgetHolder widgetHolder,
+ LauncherAppWidgetInfo info, @Nullable LauncherAppWidgetProviderInfo appWidget,
+ @Nullable Bitmap previewBitmap) {
+ this(context, widgetHolder, info, appWidget,
+ context.getResources().getText(R.string.gadget_complete_setup_text), previewBitmap);
super.updateAppWidget(null);
setOnClickListener(mActivityContext.getItemOnClickListener());
@@ -123,7 +134,7 @@
Context context, LauncherWidgetHolder widgetHolder,
int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
this(context, widgetHolder, new LauncherAppWidgetInfo(appWidgetId, appWidget.provider),
- appWidget, appWidget.label);
+ appWidget, appWidget.label, null);
getBackground().mutate().setAlpha(DEFERRED_ALPHA);
mCenterDrawable = new ColorDrawable(Color.TRANSPARENT);
@@ -132,8 +143,12 @@
mIsDeferredWidget = true;
}
- /** Set {@link Bitmap} of widget preview. */
- public void setPreviewBitmap(@Nullable Bitmap previewBitmap) {
+ /**
+ * Set {@link Bitmap} of widget preview and update background drawable. When showing preview
+ * bitmap, we shouldn't draw background.
+ */
+ public void setPreviewBitmapAndUpdateBackground(@Nullable Bitmap previewBitmap) {
+ setBackgroundResource(previewBitmap != null ? 0 : R.drawable.pending_widget_bg);
if (this.mPreviewBitmap == previewBitmap) {
return;
}
@@ -143,7 +158,8 @@
private PendingAppWidgetHostView(Context context,
LauncherWidgetHolder widgetHolder, LauncherAppWidgetInfo info,
- LauncherAppWidgetProviderInfo appwidget, CharSequence label) {
+ LauncherAppWidgetProviderInfo appwidget, CharSequence label,
+ @Nullable Bitmap previewBitmap) {
super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
mWidgetHolder = widgetHolder;
mAppwidget = appwidget;
@@ -161,7 +177,7 @@
mPreviewPaint = new Paint(ANTI_ALIAS_FLAG | DITHER_FLAG | FILTER_BITMAP_FLAG);
setWillNotDraw(false);
- setBackgroundResource(R.drawable.pending_widget_bg);
+ setPreviewBitmapAndUpdateBackground(previewBitmap);
}
@Override
@@ -440,7 +456,11 @@
protected void onDraw(Canvas canvas) {
if (mPreviewBitmap != null
&& (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0) {
- canvas.drawBitmap(mPreviewBitmap, 0, 0, mPreviewPaint);
+ mPreviewBitmapRect.set(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight());
+ mCanvasRect.set(0, 0, getWidth(), getHeight());
+
+ mMatrix.setRectToRect(mPreviewBitmapRect, mCanvasRect, Matrix.ScaleToFit.CENTER);
+ canvas.drawBitmap(mPreviewBitmap, mMatrix, mPreviewPaint);
return;
}
if (mCenterDrawable == null) {
@@ -463,7 +483,6 @@
mSetupTextLayout.draw(canvas);
canvas.restore();
}
-
}
/**
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index faad307..8857774 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -44,7 +44,6 @@
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.RoundDrawableWrapper;
-import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
/**
* Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
@@ -132,8 +131,6 @@
}
if (mAppWidgetHostViewPreview != null) {
previewSizeBeforeScale[0] = mAppWidgetHostViewPreview.getMeasuredWidth();
- launcher.getDragController()
- .addDragListener(new AppWidgetHostViewDragListener(launcher));
}
if (preview == null && mAppWidgetHostViewPreview == null) {
Drawable p = new FastBitmapDrawable(new DatabaseWidgetPreviewLoader(launcher)
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index f2f83c8..35372d3 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -18,14 +18,19 @@
import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
+import static com.android.launcher3.Flags.enableWidgetTapToAdd;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo;
import static com.android.launcher3.widget.util.WidgetSizes.getWidgetItemSizePx;
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Process;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -36,8 +41,8 @@
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -45,11 +50,13 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedPropertySetter;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.RoundDrawableWrapper;
import com.android.launcher3.model.WidgetItem;
@@ -57,6 +64,8 @@
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
+import com.android.launcher3.widget.util.WidgetSizes;
import java.util.function.Consumer;
@@ -75,23 +84,27 @@
private static final boolean DEBUG = false;
private static final int FADE_IN_DURATION_MS = 90;
+ private static final int ADD_BUTTON_FADE_DURATION_MS = 100;
/**
* The requested scale of the preview container. It can be lower than this as well.
*/
private float mPreviewContainerScale = 1f;
-
+ private Size mPreviewContainerSize = new Size(0, 0);
private FrameLayout mWidgetImageContainer;
private WidgetImageView mWidgetImage;
- private ImageView mWidgetBadge;
private TextView mWidgetName;
private TextView mWidgetDims;
private TextView mWidgetDescription;
+ private Button mWidgetAddButton;
+ private LinearLayout mWidgetTextContainer;
private WidgetItem mItem;
private Size mWidgetSize;
private final DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
+ @Nullable
+ private PreviewReadyListener mPreviewReadyListener = null;
protected CancellableTask mActiveRequest;
private boolean mAnimatePreview = true;
@@ -106,7 +119,9 @@
private int mSourceContainer = CONTAINER_WIDGETS_TRAY;
private CancellableTask mIconLoadRequest;
-
+ private boolean mIsShowingAddButton = false;
+ // Height enforced by the parent to align all widget cells displayed by it.
+ private int mParentAlignedPreviewHeight;
public WidgetCell(Context context) {
this(context, null);
}
@@ -135,10 +150,14 @@
mWidgetImageContainer = findViewById(R.id.widget_preview_container);
mWidgetImage = findViewById(R.id.widget_preview);
- mWidgetBadge = findViewById(R.id.widget_badge);
mWidgetName = findViewById(R.id.widget_name);
mWidgetDims = findViewById(R.id.widget_dims);
mWidgetDescription = findViewById(R.id.widget_description);
+ mWidgetTextContainer = findViewById(R.id.widget_text_container);
+ mWidgetAddButton = findViewById(R.id.widget_add_button);
+ if (enableWidgetTapToAdd()) {
+ mWidgetAddButton.setVisibility(INVISIBLE);
+ }
}
public void setRemoteViewsPreview(RemoteViews view) {
@@ -170,12 +189,18 @@
mWidgetImage.animate().cancel();
mWidgetImage.setDrawable(null);
mWidgetImage.setVisibility(View.VISIBLE);
- mWidgetBadge.setImageDrawable(null);
- mWidgetBadge.setVisibility(View.GONE);
mWidgetName.setText(null);
mWidgetDims.setText(null);
mWidgetDescription.setText(null);
mWidgetDescription.setVisibility(GONE);
+ mPreviewReadyListener = null;
+ mParentAlignedPreviewHeight = 0;
+ showDescription(true);
+ showDimensions(true);
+
+ if (enableWidgetTapToAdd()) {
+ hideAddButton(/* animate= */ false);
+ }
if (mActiveRequest != null) {
mActiveRequest.cancel();
@@ -186,6 +211,7 @@
mWidgetImageContainer.removeView(mAppWidgetHostViewPreview);
}
mAppWidgetHostViewPreview = null;
+ mPreviewContainerSize = new Size(0, 0);
mAppWidgetHostViewScale = 1f;
mPreviewContainerScale = 1f;
mItem = null;
@@ -201,38 +227,25 @@
* Applies the item to this view
*/
public void applyFromCellItem(WidgetItem item) {
- applyFromCellItem(item, 1f);
- }
-
- /**
- * Applies the item to this view
- */
- public void applyFromCellItem(WidgetItem item, float previewScale) {
- applyFromCellItem(item, previewScale, this::applyPreview, null);
+ applyFromCellItem(item, this::applyPreview, /*cachedPreview=*/null);
}
/**
* Applies the item to this view
* @param item item to apply
- * @param previewScale factor to scale the preview
* @param callback callback when preview is loaded in case the preview is being loaded or cached
* @param cachedPreview previously cached preview bitmap is present
*/
- public void applyFromCellItem(WidgetItem item, float previewScale,
- @NonNull Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview) {
- mPreviewContainerScale = previewScale;
-
+ public void applyFromCellItem(WidgetItem item, @NonNull Consumer<Bitmap> callback,
+ @Nullable Bitmap cachedPreview) {
Context context = getContext();
mItem = item;
mWidgetSize = getWidgetItemSizePx(getContext(), mActivity.getDeviceProfile(), mItem);
+ initPreviewContainerSizeAndScale();
mWidgetName.setText(mItem.label);
- mWidgetName.setContentDescription(
- context.getString(R.string.widget_preview_context_description, mItem.label));
mWidgetDims.setText(context.getString(R.string.widget_dims_format,
mItem.spanX, mItem.spanY));
- mWidgetDims.setContentDescription(context.getString(
- R.string.widget_accessible_dims_format, mItem.spanX, mItem.spanY));
if (!TextUtils.isEmpty(mItem.description)) {
mWidgetDescription.setText(mItem.description);
mWidgetDescription.setVisibility(VISIBLE);
@@ -240,6 +253,14 @@
mWidgetDescription.setVisibility(GONE);
}
+ // Setting the content description on the WidgetCell itself ensures that it remains
+ // screen reader focusable when the add button is showing and the text is hidden.
+ setContentDescription(createContentDescription(context));
+ if (mWidgetAddButton != null) {
+ mWidgetAddButton.setContentDescription(context.getString(
+ R.string.widget_add_button_content_description, mItem.label));
+ }
+
if (item.activityInfo != null) {
setTag(new PendingAddShortcutInfo(item.activityInfo));
} else {
@@ -278,6 +299,27 @@
}
}
+ private void initPreviewContainerSizeAndScale() {
+ WidgetPreviewContainerSize previewSize = WidgetPreviewContainerSize.Companion.forItem(mItem,
+ mActivity.getDeviceProfile());
+ mPreviewContainerSize = WidgetSizes.getWidgetSizePx(mActivity.getDeviceProfile(),
+ previewSize.spanX, previewSize.spanY);
+
+ float scaleX = (float) mPreviewContainerSize.getWidth() / mWidgetSize.getWidth();
+ float scaleY = (float) mPreviewContainerSize.getHeight() / mWidgetSize.getHeight();
+ mPreviewContainerScale = Math.min(scaleX, scaleY);
+ }
+
+ private String createContentDescription(Context context) {
+ String contentDescription =
+ context.getString(R.string.widget_preview_name_and_dims_content_description,
+ mItem.label, mItem.spanX, mItem.spanY);
+ if (!TextUtils.isEmpty(mItem.description)) {
+ contentDescription += " " + mItem.description;
+ }
+ return contentDescription;
+ }
+
private void setAppWidgetHostViewPreview(
NavigableAppWidgetHostView appWidgetHostViewPreview,
LauncherAppWidgetProviderInfo providerInfo,
@@ -301,8 +343,8 @@
private void updateAppWidgetHostScale(NavigableAppWidgetHostView view) {
// Scale the content such that all of the content is visible
- int contentWidth = view.getWidth();
- int contentHeight = view.getHeight();
+ float contentWidth = view.getWidth();
+ float contentHeight = view.getHeight();
if (view.getChildCount() == 1) {
View content = view.getChildAt(0);
@@ -322,6 +364,19 @@
mAppWidgetHostViewScale = Math.min(pWidth / contentWidth, pHeight / contentHeight);
}
view.setScaleToFit(mAppWidgetHostViewScale);
+
+ // layout based previews maybe ready at this point to inspect their inner height.
+ if (mPreviewReadyListener != null) {
+ mPreviewReadyListener.onPreviewAvailable();
+ mPreviewReadyListener = null;
+ }
+ }
+
+ /**
+ * Returns a view (holding the previews) that can be dragged and dropped.
+ */
+ public View getDragAndDropView() {
+ return mWidgetImageContainer;
}
public WidgetImageView getWidgetView() {
@@ -347,6 +402,12 @@
removeView(mAppWidgetHostViewPreview);
mAppWidgetHostViewPreview = null;
}
+
+ // Drawables of the image previews are available at this point to measure.
+ if (mPreviewReadyListener != null) {
+ mPreviewReadyListener.onPreviewAvailable();
+ mPreviewReadyListener = null;
+ }
}
if (mAnimatePreview) {
@@ -362,17 +423,6 @@
}
}
- /** Used to show the badge when the widget is in the recommended section
- */
- public void showBadge() {
- if (Process.myUserHandle().equals(mItem.user)) {
- mWidgetBadge.setVisibility(View.GONE);
- } else {
- mWidgetBadge.setVisibility(View.VISIBLE);
- mWidgetBadge.setImageResource(R.drawable.ic_work_app_badge);
- }
- }
-
/**
* Shows or hides the long description displayed below each widget.
*
@@ -384,6 +434,16 @@
}
/**
+ * Shows or hides the dimensions displayed below each widget.
+ *
+ * @param show a flag that shows the dimensions of the widget if {@code true}, hides it if
+ * {@code false}.
+ */
+ public void showDimensions(boolean show) {
+ mWidgetDims.setVisibility(show ? VISIBLE : GONE);
+ }
+
+ /**
* Set whether the app icon, for the app that provides the widget, should be shown next to the
* title text of the widget.
*
@@ -448,20 +508,76 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ViewGroup.LayoutParams containerLp = mWidgetImageContainer.getLayoutParams();
-
- mAppWidgetHostViewScale = mPreviewContainerScale;
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
- containerLp.width = Math.round(mWidgetSize.getWidth() * mAppWidgetHostViewScale);
+
+ // mPreviewContainerScale ensures the needed scaling with respect to original widget size.
+ mAppWidgetHostViewScale = mPreviewContainerScale;
+ containerLp.width = mPreviewContainerSize.getWidth();
+ int height = mPreviewContainerSize.getHeight();
+
+ // If we don't have enough available width, scale the preview container to fit.
if (containerLp.width > maxWidth) {
containerLp.width = maxWidth;
- mAppWidgetHostViewScale = (float) containerLp.width / mWidgetSize.getWidth();
+ mAppWidgetHostViewScale = (float) containerLp.width / mPreviewContainerSize.getWidth();
+ height = Math.round(mPreviewContainerSize.getHeight() * mAppWidgetHostViewScale);
}
- containerLp.height = Math.round(mWidgetSize.getHeight() * mAppWidgetHostViewScale);
- // No need to call mWidgetImageContainer.setLayoutParams as we are in measure pass
+ // Use parent aligned height in set.
+ if (mParentAlignedPreviewHeight > 0) {
+ containerLp.height = Math.min(height, mParentAlignedPreviewHeight);
+ } else {
+ containerLp.height = height;
+ }
+
+ // No need to call mWidgetImageContainer.setLayoutParams as we are in measure pass
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+
+ if (changed && isShowingAddButton()) {
+ post(this::setupIconOrTextButton);
+ }
+ }
+
+ /**
+ * Sets the height of the preview as adjusted by the parent to have this cell's content aligned
+ * with other cells displayed by the parent.
+ */
+ public void setParentAlignedPreviewHeight(int previewHeight) {
+ mParentAlignedPreviewHeight = previewHeight;
+ }
+
+ /**
+ * Returns the height of the preview without any empty space.
+ * In case of appwidget host views, it returns the height of first child. This way, if preview
+ * view provided by an app doesn't fill bounds, this will return actual height without white
+ * space.
+ */
+ public int getPreviewContentHeight() {
+ // By default assume scaled height.
+ int height = Math.round(mPreviewContainerScale * mWidgetSize.getHeight());
+
+ if (mWidgetImage != null && mWidgetImage.getDrawable() != null) {
+ // getBitmapBounds returns the scaled bounds.
+ Rect bitmapBounds = mWidgetImage.getBitmapBounds();
+ height = bitmapBounds.height();
+ } else if (mAppWidgetHostViewPreview != null
+ && mAppWidgetHostViewPreview.getChildCount() == 1) {
+ int contentHeight = Math.round(
+ mPreviewContainerScale * mWidgetSize.getHeight());
+ int previewInnerHeight = Math.round(
+ mAppWidgetHostViewScale * mAppWidgetHostViewPreview.getChildAt(
+ 0).getMeasuredHeight());
+ // Use either of the inner scaled height or the scaled widget height
+ height = Math.min(contentHeight, previewInnerHeight);
+ }
+
+ return height;
+ }
+
/**
* Loads a high resolution package icon to show next to the widget title.
*/
@@ -495,4 +611,125 @@
mIconLoadRequest = null;
}
}
+
+ /**
+ * Show tap to add button.
+ * @param callback Callback to be set on the button.
+ */
+ public void showAddButton(View.OnClickListener callback) {
+ if (mIsShowingAddButton) return;
+ mIsShowingAddButton = true;
+
+ setupIconOrTextButton();
+ mWidgetAddButton.setOnClickListener(callback);
+ fadeThrough(/* hide= */ mWidgetTextContainer, /* show= */ mWidgetAddButton,
+ ADD_BUTTON_FADE_DURATION_MS, Interpolators.LINEAR);
+ }
+
+ /**
+ * Depending on the width of the cell, set up the add button to be icon-only or icon+text.
+ */
+ private void setupIconOrTextButton() {
+ String addText = getResources().getString(R.string.widget_add_button_label);
+ Rect textSize = new Rect();
+ mWidgetAddButton.getPaint().getTextBounds(addText, 0, addText.length(), textSize);
+ int startPadding = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_button_start_padding);
+ int endPadding = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_button_end_padding);
+ int drawableWidth = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_button_drawable_width);
+ int drawablePadding = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_button_drawable_padding);
+ int textButtonWidth = textSize.width() + startPadding + endPadding + drawableWidth
+ + drawablePadding;
+ if (textButtonWidth > getMeasuredWidth()) {
+ // Setup icon-only button
+ mWidgetAddButton.setText(null);
+ int startIconPadding = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_icon_button_start_padding);
+ mWidgetAddButton.setPaddingRelative(/* start= */ startIconPadding, /* top= */ 0,
+ /* end= */ endPadding, /* bottom= */ 0);
+ mWidgetAddButton.setCompoundDrawablePadding(0);
+ } else {
+ // Setup icon + text button
+ mWidgetAddButton.setText(addText);
+ mWidgetAddButton.setPaddingRelative(/* start= */ startPadding, /* top= */ 0,
+ /* end= */ endPadding, /* bottom= */ 0);
+ mWidgetAddButton.setCompoundDrawablePadding(drawablePadding);
+ }
+ }
+
+ /**
+ * Hide tap to add button.
+ */
+ public void hideAddButton(boolean animate) {
+ if (!mIsShowingAddButton) return;
+ mIsShowingAddButton = false;
+
+ mWidgetAddButton.setOnClickListener(null);
+
+ if (!animate) {
+ mWidgetAddButton.setVisibility(INVISIBLE);
+ mWidgetTextContainer.setVisibility(VISIBLE);
+ mWidgetTextContainer.setAlpha(1F);
+ return;
+ }
+
+ fadeThrough(/* hide= */ mWidgetAddButton, /* show= */ mWidgetTextContainer,
+ ADD_BUTTON_FADE_DURATION_MS, Interpolators.LINEAR);
+ }
+
+ public boolean isShowingAddButton() {
+ return mIsShowingAddButton;
+ }
+
+ private static void fadeThrough(View hide, View show, int durationMs,
+ TimeInterpolator interpolator) {
+ AnimatedPropertySetter setter = new AnimatedPropertySetter();
+
+ Animator hideAnim = setter.setViewAlpha(hide, 0F, interpolator).setDuration(durationMs);
+ if (hideAnim instanceof ObjectAnimator anim) {
+ anim.setAutoCancel(true);
+ }
+
+ Animator showAnim = setter.setViewAlpha(show, 1F, interpolator).setDuration(durationMs);
+ if (showAnim instanceof ObjectAnimator anim) {
+ anim.setAutoCancel(true);
+ }
+
+ AnimatorSet set = new AnimatorSet();
+ set.playSequentially(hideAnim, showAnim);
+ set.start();
+ }
+
+ /**
+ * Returns true if this WidgetCell is displaying the same item as info.
+ */
+ public boolean matchesItem(WidgetItem info) {
+ if (info == null || mItem == null) return false;
+ if (info.widgetInfo != null && mItem.widgetInfo != null) {
+ return info.widgetInfo.getUser().equals(mItem.widgetInfo.getUser())
+ && info.widgetInfo.getComponent().equals(mItem.widgetInfo.getComponent());
+ } else if (info.activityInfo != null && mItem.activityInfo != null) {
+ return info.activityInfo.getUser().equals(mItem.activityInfo.getUser())
+ && info.activityInfo.getComponent().equals(mItem.activityInfo.getComponent());
+ }
+ return false;
+ }
+
+ /**
+ * Listener to notify when previews are available.
+ */
+ public void addPreviewReadyListener(PreviewReadyListener previewReadyListener) {
+ mPreviewReadyListener = previewReadyListener;
+ }
+
+ /**
+ * Listener interface for subscribers to listen to preview's availability.
+ */
+ public interface PreviewReadyListener {
+ /** Handler on to invoke when previews are available. */
+ void onPreviewAvailable();
+ }
}
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index 11f4485..f332054 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -24,8 +24,6 @@
import android.util.AttributeSet;
import android.view.View;
-import com.android.launcher3.R;
-
/**
* View that draws a bitmap horizontally centered. If the image width is greater than the view
* width, the image is scaled down appropriately.
@@ -33,8 +31,6 @@
public class WidgetImageView extends View {
private final RectF mDstRectF = new RectF();
- private final int mBadgeMargin;
-
private Drawable mDrawable;
public WidgetImageView(Context context) {
@@ -47,9 +43,6 @@
public WidgetImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
-
- mBadgeMargin = context.getResources()
- .getDimensionPixelSize(R.dimen.profile_badge_margin);
}
/** Set the drawable to use for this view. */
@@ -82,15 +75,27 @@
private void updateDstRectF() {
float myWidth = getWidth();
float myHeight = getHeight();
- float bitmapWidth = mDrawable.getIntrinsicWidth();
+ final float bitmapWidth = mDrawable.getIntrinsicWidth();
+ final float bitmapHeight = mDrawable.getIntrinsicHeight();
+ final float bitmapAspectRatio = bitmapWidth / bitmapHeight;
+ final float containerAspectRatio = myWidth / myHeight;
- final float scale = bitmapWidth > myWidth ? myWidth / bitmapWidth : 1;
- float scaledWidth = bitmapWidth * scale;
- float scaledHeight = mDrawable.getIntrinsicHeight() * scale;
+ // Scale by width if image has larger aspect ratio than the container else by height; and
+ // avoid cropping the previews
+ final float scale = bitmapAspectRatio > containerAspectRatio ? myWidth / bitmapWidth
+ : myHeight / bitmapHeight;
- mDstRectF.left = (myWidth - scaledWidth) / 2;
- mDstRectF.right = (myWidth + scaledWidth) / 2;
+ final float scaledWidth = bitmapWidth * scale;
+ final float scaledHeight = bitmapHeight * scale;
+ // Avoid cropping by checking bounds after scaling.
+ if (scaledWidth > myWidth) {
+ mDstRectF.left = 0;
+ mDstRectF.right = scaledWidth;
+ } else {
+ mDstRectF.left = (myWidth - scaledWidth) / 2;
+ mDstRectF.right = (myWidth + scaledWidth) / 2;
+ }
if (scaledHeight > myHeight) {
mDstRectF.top = 0;
mDstRectF.bottom = scaledHeight;
diff --git a/src/com/android/launcher3/widget/WidgetInflater.kt b/src/com/android/launcher3/widget/WidgetInflater.kt
index dd50b71..271c9c2 100644
--- a/src/com/android/launcher3/widget/WidgetInflater.kt
+++ b/src/com/android/launcher3/widget/WidgetInflater.kt
@@ -17,11 +17,11 @@
package com.android.launcher3.widget
import android.content.Context
+import com.android.launcher3.BuildConfig
import com.android.launcher3.Launcher
import com.android.launcher3.LauncherAppState
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
import com.android.launcher3.logging.FileLog
-import com.android.launcher3.model.WidgetsModel
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.qsb.QsbContainerView
@@ -55,7 +55,7 @@
// The widget id is not valid. Try to find the widget based on the provider info.
appWidgetInfo = widgetHelper.findProvider(item.providerName, item.user)
if (appWidgetInfo == null) {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!BuildConfig.WIDGETS_ENABLED) {
removalReason = "widgets are disabled on go device."
logReason = RestoreError.WIDGETS_DISABLED
} else {
diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java
index 52767a4..9132b4f 100644
--- a/src/com/android/launcher3/widget/WidgetManagerHelper.java
+++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java
@@ -16,6 +16,8 @@
package com.android.launcher3.widget;
+import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
+
import android.annotation.TargetApi;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -24,13 +26,14 @@
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
+import android.util.Log;
import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.PackageUserKey;
@@ -46,6 +49,7 @@
*/
public class WidgetManagerHelper {
+ private static final String TAG = "WidgetManagerHelper";
//TODO: replace this with OPTION_APPWIDGET_RESTORE_COMPLETED b/63667276
public static final String WIDGET_OPTION_RESTORE_COMPLETED = "appWidgetRestoreCompleted";
@@ -60,6 +64,7 @@
/**
* @see AppWidgetManager#getAppWidgetInfo(int)
*/
+ @Nullable
public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(
int appWidgetId, ComponentName componentName) {
@@ -69,7 +74,14 @@
}
AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
- return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
+ if (info == null) {
+ FileLog.w(TAG,
+ "getLauncherAppWidgetInfo: AppWidgetManager returned null AppWidgetInfo for"
+ + " appWidgetId=" + appWidgetId
+ + ", componentName=" + componentName);
+ return null;
+ }
+ return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
}
/**
@@ -77,7 +89,7 @@
*/
@TargetApi(Build.VERSION_CODES.O)
public List<AppWidgetProviderInfo> getAllProviders(@Nullable PackageUserKey packageUser) {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
return Collections.emptyList();
}
@@ -93,6 +105,8 @@
// If exception is thrown because of device is locked, it means a race condition occurs
// that the user got locked again while launcher is processing the event. In this case
// we should return empty list.
+ Log.e(TAG, "getAllProviders: Error getting installed providers for"
+ + " package=" + packageUser.mPackageName, e);
return Collections.emptyList();
}
}
@@ -102,7 +116,7 @@
*/
public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
Bundle options) {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
return false;
}
if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
@@ -113,7 +127,7 @@
}
public LauncherAppWidgetProviderInfo findProvider(ComponentName provider, UserHandle user) {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WIDGETS_ENABLED) {
return null;
}
for (AppWidgetProviderInfo info :
@@ -122,6 +136,8 @@
return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
}
}
+ Log.w(TAG, "findProvider: No App Widget Provider found for component=" + provider
+ + " user=" + user);
return null;
}
@@ -129,17 +145,16 @@
* Returns if a AppWidgetProvider has marked a widget restored
*/
public boolean isAppWidgetRestored(int appWidgetId) {
- return !WidgetsModel.GO_DISABLE_WIDGETS && mAppWidgetManager.getAppWidgetOptions(
- appWidgetId).getBoolean(WIDGET_OPTION_RESTORE_COMPLETED);
+ return WIDGETS_ENABLED && mAppWidgetManager.getAppWidgetOptions(appWidgetId)
+ .getBoolean(WIDGET_OPTION_RESTORE_COMPLETED);
}
/**
* Load RemoteViews preview for this provider if available.
*
- * @param info The provider info for the widget you want to preview.
+ * @param info The provider info for the widget you want to preview.
* @param widgetCategory The widget category for which you want to display previews.
- *
* @return Returns the widget preview that matches selected category, if available.
*/
@Nullable
diff --git a/src/com/android/launcher3/widget/WidgetTableRow.java b/src/com/android/launcher3/widget/WidgetTableRow.java
new file mode 100644
index 0000000..a5312ce
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetTableRow.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TableRow;
+
+/**
+ * A row of {@link WidgetCell}s that can be displayed in a table.
+ */
+public class WidgetTableRow extends TableRow implements WidgetCell.PreviewReadyListener {
+ private int mNumOfReadyCells;
+ private int mNumOfCells;
+ private int mResizeDelay;
+
+ public WidgetTableRow(Context context) {
+ super(context);
+ }
+ public WidgetTableRow(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void onPreviewAvailable() {
+ mNumOfReadyCells++;
+
+ // Once all previews are loaded, find max visible height and adjust the preview containers.
+ if (mNumOfReadyCells == mNumOfCells) {
+ resize();
+ }
+ }
+
+ private void resize() {
+ int previewHeight = 0;
+ // get the maximum height of each widget preview
+ for (int i = 0; i < getChildCount(); i++) {
+ WidgetCell widgetCell = (WidgetCell) getChildAt(i);
+ previewHeight = Math.max(widgetCell.getPreviewContentHeight(), previewHeight);
+ }
+ if (mResizeDelay > 0) {
+ postDelayed(() -> setAlpha(1f), mResizeDelay);
+ }
+ if (previewHeight > 0) {
+ for (int i = 0; i < getChildCount(); i++) {
+ WidgetCell widgetCell = (WidgetCell) getChildAt(i);
+ widgetCell.setParentAlignedPreviewHeight(previewHeight);
+ widgetCell.postDelayed(widgetCell::requestLayout, mResizeDelay);
+ }
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ }
+
+ /**
+ * Sets up the row to display the provided number of numOfCells.
+ *
+ * @param numOfCells number of numOfCells in the row
+ * @param resizeDelayMs time to wait in millis before making any layout size adjustments e.g. we
+ * want to wait for list expand collapse animation before resizing the
+ * cell previews.
+ */
+ public void setupRow(int numOfCells, int resizeDelayMs) {
+ mNumOfCells = numOfCells;
+ mNumOfReadyCells = 0;
+
+ mResizeDelay = resizeDelayMs;
+ // For delayed resize, reveal contents only after resize is done.
+ if (mResizeDelay > 0) {
+ setAlpha(0);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index ceb0072..894099d 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -16,23 +16,21 @@
package com.android.launcher3.widget;
-import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_BOTTOM_WIDGETS_TRAY;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.IntProperty;
import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.animation.Interpolator;
import android.widget.ScrollView;
import android.widget.TableLayout;
-import android.widget.TableRow;
import android.widget.TextView;
import androidx.annotation.Px;
@@ -50,57 +48,11 @@
* Bottom sheet for the "Widgets" system shortcut in the long-press popup.
*/
public class WidgetsBottomSheet extends BaseWidgetSheet {
- private static final String TAG = "WidgetsBottomSheet";
-
- private static final IntProperty<View> PADDING_BOTTOM =
- new IntProperty<View>("paddingBottom") {
- @Override
- public void setValue(View view, int paddingBottom) {
- view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
- view.getPaddingRight(), paddingBottom);
- }
-
- @Override
- public Integer get(View view) {
- return view.getPaddingBottom();
- }
- };
-
private static final int DEFAULT_CLOSE_DURATION = 200;
- private static final long EDUCATION_TIP_DELAY_MS = 300;
private ItemInfo mOriginalItemInfo;
@Px private int mMaxHorizontalSpan;
- private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
- new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- if (hasSeenEducationTip()) {
- removeOnLayoutChangeListener(this);
- return;
- }
- // Widgets are loaded asynchronously, We are adding a delay because we only want
- // to show the tip when the widget preview has finished loading and rendering in
- // this view.
- removeCallbacks(mShowEducationTipTask);
- postDelayed(mShowEducationTipTask, EDUCATION_TIP_DELAY_MS);
- }
- };
-
- private final Runnable mShowEducationTipTask = () -> {
- if (hasSeenEducationTip()) {
- removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
- return;
- }
- View viewForTip = ((ViewGroup) ((TableLayout) findViewById(R.id.widgets_table))
- .getChildAt(0)).getChildAt(0);
- if (showEducationTipOnViewIfPossible(viewForTip) != null) {
- removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
- }
- };
-
public WidgetsBottomSheet(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -108,9 +60,6 @@
public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
- if (!hasSeenEducationTip()) {
- addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
- }
}
@Override
@@ -187,17 +136,15 @@
mActivityContext.getDeviceProfile(), mMaxHorizontalSpan,
mWidgetCellHorizontalPadding)
.forEach(row -> {
- TableRow tableRow = new TableRow(getContext());
- if (enableCategorizedWidgetSuggestions()) {
- // Vertically center align items, so that even if they don't fill bounds,
- // they can look organized when placed together in a row.
- tableRow.setGravity(Gravity.CENTER_VERTICAL);
- } else {
- tableRow.setGravity(Gravity.TOP);
- }
+ WidgetTableRow tableRow = new WidgetTableRow(getContext());
+ tableRow.setGravity(Gravity.TOP);
+ tableRow.setupRow(row.size(), /*resizeDelayMs=*/ 0);
row.forEach(widgetItem -> {
WidgetCell widget = addItemCell(tableRow);
widget.applyFromCellItem(widgetItem);
+ if (widget.matchesItem(getLastSelectedWidgetItem())) {
+ widget.callOnClick();
+ }
});
widgetsTable.addView(tableRow);
});
@@ -216,9 +163,11 @@
return super.onControllerInterceptTouchEvent(ev);
}
- protected WidgetCell addItemCell(ViewGroup parent) {
+ protected WidgetCell addItemCell(WidgetTableRow parent) {
WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext())
.inflate(R.layout.widget_cell, parent, false);
+ widget.addPreviewReadyListener(parent);
+ widget.setOnClickListener(this);
View previewContainer = widget.findViewById(R.id.widget_preview_container);
previewContainer.setOnClickListener(this);
@@ -284,6 +233,18 @@
@Override
public void addHintCloseAnim(
float distanceToMove, Interpolator interpolator, PendingAnimation target) {
- target.setInt(this, PADDING_BOTTOM, (int) (distanceToMove + mInsets.bottom), interpolator);
+ target.addAnimatedFloat(mSwipeToDismissProgress, 0f, 1f, interpolator);
+ }
+
+ @Override
+ protected void scrollCellContainerByY(WidgetCell wc, int scrollByY) {
+ for (ViewParent parent = wc.getParent(); parent != null; parent = parent.getParent()) {
+ if (parent instanceof ScrollView scrollView) {
+ scrollView.smoothScrollBy(0, scrollByY);
+ return;
+ } else if (parent == this) {
+ return;
+ }
+ }
}
}
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index 2fdf354..50012b3 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -16,7 +16,7 @@
package com.android.launcher3.widget.custom;
-import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
+import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
import static com.android.launcher3.model.data.LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
@@ -32,9 +32,9 @@
import androidx.annotation.Nullable;
import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -70,7 +70,7 @@
PluginManagerWrapper.INSTANCE.get(context)
.addPluginListener(this, CustomWidgetPlugin.class, true);
- if (SMARTSPACE_AS_A_WIDGET.get()) {
+ if (enableSmartspaceAsAWidget()) {
for (String s: context.getResources()
.getStringArray(R.array.custom_widget_providers)) {
try {
diff --git a/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java b/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java
deleted file mode 100644
index 3e54b33..0000000
--- a/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2021 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.dragndrop;
-
-import com.android.launcher3.DropTarget;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
-
-/** A drag listener of {@link LauncherAppWidgetHostView}. */
-public final class AppWidgetHostViewDragListener implements DragController.DragListener {
- private final Launcher mLauncher;
- private LauncherAppWidgetHostView mAppWidgetHostView;
-
- public AppWidgetHostViewDragListener(Launcher launcher) {
- mLauncher = launcher;
- }
-
- @Override
- public void onDragStart(DropTarget.DragObject dragObject, DragOptions unused) {
- if (dragObject.dragView.getContentView() instanceof LauncherAppWidgetHostView) {
- mAppWidgetHostView = (LauncherAppWidgetHostView) dragObject.dragView.getContentView();
- mAppWidgetHostView.startDrag();
- } else {
- mLauncher.getDragController().removeDragListener(this);
- }
- }
-
- @Override
- public void onDragEnd() {
- mAppWidgetHostView.endDrag();
- mLauncher.getDragController().removeDragListener(this);
- }
-}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
index 68f18ae..0d775c3 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
@@ -36,41 +36,49 @@
(context, entry) -> entry.mWidgets.stream()
.map(item -> item.label).sorted().collect(Collectors.joining(", "));
- private static final BiFunction<Context, WidgetsListHeaderEntry, String> SUBTITLE_DEFAULT =
- (context, entry) -> {
- List<WidgetItem> items = entry.mWidgets;
- int wc = (int) items.stream().filter(item -> item.widgetInfo != null).count();
- int sc = Math.max(0, items.size() - wc);
+ @Nullable
+ private static String buildWidgetsCountString(Context context, int wc, int sc) {
+ Resources resources = context.getResources();
+ if (wc == 0 && sc == 0) {
+ return null;
+ }
- Resources resources = context.getResources();
- if (wc == 0 && sc == 0) {
- return null;
- }
-
- String subtitle;
- if (wc > 0 && sc > 0) {
- String widgetsCount = PluralMessageFormat.getIcuPluralString(context,
- R.string.widgets_count, wc);
- String shortcutsCount = PluralMessageFormat.getIcuPluralString(context,
- R.string.shortcuts_count, sc);
- subtitle = resources.getString(R.string.widgets_and_shortcuts_count,
- widgetsCount, shortcutsCount);
- } else if (wc > 0) {
- subtitle = PluralMessageFormat.getIcuPluralString(context,
- R.string.widgets_count, wc);
- } else {
- subtitle = PluralMessageFormat.getIcuPluralString(context,
- R.string.shortcuts_count, sc);
- }
- return subtitle;
- };
+ String subtitle;
+ if (wc > 0 && sc > 0) {
+ String widgetsCount = PluralMessageFormat.getIcuPluralString(context,
+ R.string.widgets_count, wc);
+ String shortcutsCount = PluralMessageFormat.getIcuPluralString(context,
+ R.string.shortcuts_count, sc);
+ subtitle = resources.getString(R.string.widgets_and_shortcuts_count,
+ widgetsCount, shortcutsCount);
+ } else if (wc > 0) {
+ subtitle = PluralMessageFormat.getIcuPluralString(context,
+ R.string.widgets_count, wc);
+ } else {
+ subtitle = PluralMessageFormat.getIcuPluralString(context,
+ R.string.shortcuts_count, sc);
+ }
+ return subtitle;
+ }
private final boolean mIsWidgetListShown;
+ /** Selected widgets displayed */
+ private final int mVisibleWidgetsCount;
private final boolean mIsSearchEntry;
private WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
+ List<WidgetItem> items, int visibleWidgetsCount,
+ boolean isSearchEntry, boolean isWidgetListShown) {
+ super(pkgItem, titleSectionName, items);
+ mVisibleWidgetsCount = visibleWidgetsCount;
+ mIsSearchEntry = isSearchEntry;
+ mIsWidgetListShown = isWidgetListShown;
+ }
+
+ private WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
List<WidgetItem> items, boolean isSearchEntry, boolean isWidgetListShown) {
super(pkgItem, titleSectionName, items);
+ mVisibleWidgetsCount = (int) items.stream().filter(w -> w.widgetInfo != null).count();
mIsSearchEntry = isSearchEntry;
mIsWidgetListShown = isWidgetListShown;
}
@@ -91,8 +99,13 @@
@Nullable
public String getSubtitle(Context context) {
- return mIsSearchEntry
- ? SUBTITLE_SEARCH.apply(context, this) : SUBTITLE_DEFAULT.apply(context, this);
+ if (mIsSearchEntry) {
+ return SUBTITLE_SEARCH.apply(context, this);
+ } else {
+ int shortcutsCount = Math.max(0,
+ (int) mWidgets.stream().filter(WidgetItem::isShortcut).count());
+ return buildWidgetsCountString(context, mVisibleWidgetsCount, shortcutsCount);
+ }
}
@Override
@@ -102,6 +115,7 @@
return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
&& mTitleSectionName.equals(otherEntry.mTitleSectionName)
&& mIsWidgetListShown == otherEntry.mIsWidgetListShown
+ && mVisibleWidgetsCount == otherEntry.mVisibleWidgetsCount
&& mIsSearchEntry == otherEntry.mIsSearchEntry;
}
@@ -112,6 +126,7 @@
mPkgItem,
mTitleSectionName,
mWidgets,
+ mVisibleWidgetsCount,
mIsSearchEntry,
/* isWidgetListShown= */ true);
}
@@ -122,7 +137,28 @@
pkgItem,
titleSectionName,
items,
- /* forSearch */ false,
+ /* isSearchEntry= */ false,
+ /* isWidgetListShown= */ false);
+ }
+
+ /**
+ * Creates a widget list holder for an header ("app" / "suggestions") which has widgets or/and
+ * shortcuts.
+ *
+ * @param pkgItem package item info for the header section
+ * @param titleSectionName title string for the header
+ * @param items all items for the given header
+ * @param visibleWidgetsCount widgets count when only selected widgets are shown due to
+ * limited space.
+ */
+ public static WidgetsListHeaderEntry create(PackageItemInfo pkgItem, String titleSectionName,
+ List<WidgetItem> items, int visibleWidgetsCount) {
+ return new WidgetsListHeaderEntry(
+ pkgItem,
+ titleSectionName,
+ items,
+ visibleWidgetsCount,
+ /* isSearchEntry= */ false,
/* isWidgetListShown= */ false);
}
@@ -132,7 +168,7 @@
pkgItem,
titleSectionName,
items,
- /* forSearch */ true,
+ /* isSearchEntry */ true,
/* isWidgetListShown= */ false);
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
index 801b1f6..9253b37 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
@@ -18,13 +18,13 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -54,6 +54,7 @@
* to display the recommendation grouped by categories.
*/
@WorkerThread
+ @Nullable
public WidgetRecommendationCategory getWidgetRecommendationCategory(Context context,
WidgetItem item) {
// This is a default implementation that uses application category to derive the category to
@@ -61,17 +62,14 @@
// via the overridden WidgetRecommendationCategoryProvider resource.
Preconditions.assertWorkerThread();
- PackageManager pm = context.getPackageManager();
- if (item.widgetInfo != null && item.widgetInfo.getComponent() != null) {
- String widgetComponentName = item.widgetInfo.getComponent().getClassName();
- try {
- int predictionCategory = pm.getApplicationInfo(
- item.widgetInfo.getComponent().getPackageName(), 0 /* flags */).category;
- return getCategoryFromApplicationCategory(context, predictionCategory,
- widgetComponentName);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Failed to retrieve application category when determining the "
- + "widget category for " + widgetComponentName, e);
+ try (PackageManagerHelper pmHelper = new PackageManagerHelper(context)) {
+ if (item.widgetInfo != null && item.widgetInfo.getComponent() != null) {
+ ApplicationInfo applicationInfo = pmHelper.getApplicationInfo(
+ item.widgetInfo.getComponent().getPackageName(), item.widgetInfo.getUser(),
+ 0 /* flags */);
+ if (applicationInfo != null) {
+ return getCategoryFromApplicationCategory(applicationInfo.category);
+ }
}
}
return null;
@@ -79,29 +77,7 @@
/** Maps application category to an appropriate displayable category. */
private static WidgetRecommendationCategory getCategoryFromApplicationCategory(
- Context context, int applicationCategory, String componentName) {
- // Weather categories don't map to a specific application category, so, we maintain an
- // allowlist.
- String[] weatherRecommendationAllowlist =
- context.getResources().getStringArray(R.array.weather_recommendations);
- for (String allowedWeatherComponentName : weatherRecommendationAllowlist) {
- if (componentName.equalsIgnoreCase(allowedWeatherComponentName)) {
- return new WidgetRecommendationCategory(
- R.string.weather_widget_recommendation_category_label, /*order=*/3);
- }
- }
-
- // Fitness categories don't map to a specific application category, so, we maintain an
- // allowlist.
- String[] fitnessRecommendationAllowlist =
- context.getResources().getStringArray(R.array.fitness_recommendations);
- for (String allowedFitnessComponentName : fitnessRecommendationAllowlist) {
- if (componentName.equalsIgnoreCase(allowedFitnessComponentName)) {
- return new WidgetRecommendationCategory(
- R.string.fitness_widget_recommendation_category_label, /*order=*/2);
- }
- }
-
+ int applicationCategory) {
if (applicationCategory == ApplicationInfo.CATEGORY_PRODUCTIVITY) {
return new WidgetRecommendationCategory(
R.string.productivity_widget_recommendation_category_label, /*order=*/0);
@@ -112,17 +88,21 @@
R.string.news_widget_recommendation_category_label, /*order=*/1);
}
- if (applicationCategory == ApplicationInfo.CATEGORY_SOCIAL
- || applicationCategory == ApplicationInfo.CATEGORY_AUDIO
+ if (applicationCategory == ApplicationInfo.CATEGORY_SOCIAL) {
+ return new WidgetRecommendationCategory(
+ R.string.social_widget_recommendation_category_label,
+ /*order=*/3);
+ }
+
+ if (applicationCategory == ApplicationInfo.CATEGORY_AUDIO
|| applicationCategory == ApplicationInfo.CATEGORY_VIDEO
|| applicationCategory == ApplicationInfo.CATEGORY_IMAGE) {
return new WidgetRecommendationCategory(
- R.string.social_and_entertainment_widget_recommendation_category_label,
+ R.string.entertainment_widget_recommendation_category_label,
/*order=*/4);
}
return new WidgetRecommendationCategory(
- R.string.others_widget_recommendation_category_label, /*order=*/5);
+ R.string.others_widget_recommendation_category_label, /*order=*/2);
}
-
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index 426a3ae..9260af9 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -18,7 +18,9 @@
import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering;
+import android.content.ComponentName;
import android.content.Context;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,13 +33,19 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* A {@link PagedView} that displays widget recommendations in categories with dots as paged
@@ -45,15 +53,21 @@
*/
public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots> {
private @Px float mAvailableHeight = Float.MAX_VALUE;
-
+ private @Px float mAvailableWidth = 0;
+ private static final String INITIALLY_DISPLAYED_WIDGETS_STATE_KEY =
+ "widgetRecommendationsView:mDisplayedWidgets";
private static final int MAX_CATEGORIES = 3;
private TextView mRecommendationPageTitle;
private final List<String> mCategoryTitles = new ArrayList<>();
+ /** Callbacks to run when page changes */
+ private final List<Consumer<Integer>> mPageSwitchListeners = new ArrayList<>();
+
@Nullable
private OnLongClickListener mWidgetCellOnLongClickListener;
@Nullable
private OnClickListener mWidgetCellOnClickListener;
+ private Set<ComponentName> mDisplayedWidgets = Collections.emptySet();
public WidgetRecommendationsView(Context context) {
this(context, /* attrs= */ null);
@@ -73,6 +87,38 @@
mRecommendationPageTitle = parent.findViewById(R.id.recommendations_page_title);
}
+ /**
+ * Saves the necessary state in the provided bundle. To be called in case of orientation /
+ * other config changes.
+ */
+ public void saveState(Bundle bundle) {
+ // Save the widgets that were displayed, so that, on rotation / fold / unfold, we can
+ // maintain the "initial" set of widgets that user first saw (if they fit).
+ bundle.putParcelableArrayList(INITIALLY_DISPLAYED_WIDGETS_STATE_KEY,
+ new ArrayList<>(mDisplayedWidgets));
+ }
+
+ /**
+ * Restores the state that was saved by the saveState method during orientation / other config
+ * changes.
+ */
+ public void restoreState(Bundle bundle) {
+ ArrayList<ComponentName> componentList;
+ if (Utilities.ATLEAST_T) {
+ componentList = bundle.getParcelableArrayList(
+ INITIALLY_DISPLAYED_WIDGETS_STATE_KEY, ComponentName.class);
+ } else {
+ componentList = bundle.getParcelableArrayList(
+ INITIALLY_DISPLAYED_WIDGETS_STATE_KEY);
+ }
+
+ // Restore the "initial" set of widgets that were displayed, so that, on rotation / fold /
+ // unfold, we can maintain the set of widgets that user first saw (if they fit).
+ if (componentList != null) {
+ mDisplayedWidgets = new HashSet<>(componentList);
+ }
+ }
+
/** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
mWidgetCellOnLongClickListener = onLongClickListener;
@@ -84,6 +130,13 @@
}
/**
+ * Add a callback to run when the current displayed page changes.
+ */
+ public void addPageSwitchListener(Consumer<Integer> pageChangeListener) {
+ mPageSwitchListeners.add(pageChangeListener);
+ }
+
+ /**
* Displays all the provided recommendations in a single table if they fit.
*
* @param recommendedWidgets list of widgets to be displayed in recommendation section.
@@ -93,18 +146,28 @@
* @param availableWidth width in px that the recommendations should display in
* @param cellPadding padding in px that should be applied to each widget in the
* recommendations
- * @return {@code false} if no recommendations could fit in the available space.
+ * @return number of recommendations that could fit in the available space.
*/
- public boolean setRecommendations(
+ public int setRecommendations(
List<WidgetItem> recommendedWidgets, DeviceProfile deviceProfile,
final @Px float availableHeight, final @Px int availableWidth,
final @Px int cellPadding) {
this.mAvailableHeight = availableHeight;
- removeAllViews();
+ this.mAvailableWidth = availableWidth;
+ clear();
- maybeDisplayInTable(recommendedWidgets, deviceProfile, availableWidth, cellPadding);
- updateTitleAndIndicator();
- return getChildCount() > 0;
+ Set<ComponentName> displayedWidgets = maybeDisplayInTable(recommendedWidgets,
+ deviceProfile,
+ availableWidth, cellPadding);
+
+ if (mDisplayedWidgets.isEmpty()) {
+ // Save the widgets shown for the first time user opened the picker; so that, they can
+ // be maintained across orientation changes.
+ mDisplayedWidgets = displayedWidgets;
+ }
+
+ updateTitleAndIndicator(/* requestedPage= */ 0);
+ return displayedWidgets.size();
}
/**
@@ -118,29 +181,37 @@
* @param availableWidth width in px that the recommendations should display in
* @param cellPadding padding in px that should be applied to each widget in the
* recommendations
- * @return {@code false} if no recommendations could fit in the available space.
+ * @param requestedPage page number to display initially.
+ * @return number of recommendations that could fit in the available space.
*/
- public boolean setRecommendations(
+ public int setRecommendations(
Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations,
- DeviceProfile deviceProfile,
- final @Px float availableHeight, final @Px int availableWidth,
- final @Px int cellPadding) {
+ DeviceProfile deviceProfile, final @Px float availableHeight,
+ final @Px int availableWidth, final @Px int cellPadding, final int requestedPage) {
this.mAvailableHeight = availableHeight;
+ this.mAvailableWidth = availableWidth;
Context context = getContext();
- mPageIndicator.setPauseScroll(true, deviceProfile.isTwoPanels);
- removeAllViews();
+ // For purpose of recommendations section, we don't want paging dots to be halved in two
+ // pane display, so, we always provide isTwoPanels = "false".
+ mPageIndicator.setPauseScroll(/*pause=*/true, /*isTwoPanels=*/ false);
+ clear();
int displayedCategories = 0;
+ Set<ComponentName> allDisplayedWidgets = new HashSet<>();
// Render top MAX_CATEGORIES in separate tables. Each table becomes a page.
for (Map.Entry<WidgetRecommendationCategory, List<WidgetItem>> entry :
new TreeMap<>(recommendations).entrySet()) {
// If none of the recommendations for the category could fit in the mAvailableHeight, we
// don't want to add that category; and we look for the next one.
- if (maybeDisplayInTable(entry.getValue(), deviceProfile, availableWidth, cellPadding)) {
+ Set<ComponentName> displayedWidgetsForCategory = maybeDisplayInTable(entry.getValue(),
+ deviceProfile,
+ availableWidth, cellPadding);
+ if (!displayedWidgetsForCategory.isEmpty()) {
mCategoryTitles.add(
context.getResources().getString(entry.getKey().categoryTitleRes));
displayedCategories++;
+ allDisplayedWidgets.addAll(displayedWidgetsForCategory);
}
if (displayedCategories == MAX_CATEGORIES) {
@@ -148,21 +219,39 @@
}
}
- updateTitleAndIndicator();
- mPageIndicator.setPauseScroll(false, deviceProfile.isTwoPanels);
- return getChildCount() > 0;
+ if (mDisplayedWidgets.isEmpty()) {
+ // Save the widgets shown for the first time user opened the picker; so that, they can
+ // be maintained across orientation changes.
+ mDisplayedWidgets = allDisplayedWidgets;
+ }
+
+ updateTitleAndIndicator(requestedPage);
+ // For purpose of recommendations section, we don't want paging dots to be halved in two
+ // pane display, so, we always provide isTwoPanels = "false".
+ mPageIndicator.setPauseScroll(/*pause=*/false, /*isTwoPanels=*/false);
+ return allDisplayedWidgets.size();
+ }
+
+ private void clear() {
+ mCategoryTitles.clear();
+ removeAllViews();
+ setCurrentPage(0);
+ mPageIndicator.setActiveMarker(0);
}
/** Displays the page title and paging indicator if there are multiple pages. */
- private void updateTitleAndIndicator() {
+ private void updateTitleAndIndicator(int requestedPage) {
boolean showPaginatedView = getPageCount() > 1;
int titleAndIndicatorVisibility = showPaginatedView ? View.VISIBLE : View.GONE;
mRecommendationPageTitle.setVisibility(titleAndIndicatorVisibility);
mPageIndicator.setVisibility(titleAndIndicatorVisibility);
if (showPaginatedView) {
- mPageIndicator.setActiveMarker(0);
- setCurrentPage(0);
- mRecommendationPageTitle.setText(mCategoryTitles.get(0));
+ if (requestedPage <= 0 || requestedPage >= getPageCount()) {
+ requestedPage = 0;
+ }
+ setCurrentPage(requestedPage);
+ mPageIndicator.setActiveMarker(requestedPage);
+ mRecommendationPageTitle.setText(mCategoryTitles.get(requestedPage));
}
}
@@ -170,9 +259,10 @@
protected void notifyPageSwitchListener(int prevPage) {
if (getPageCount() > 1) {
// Since the title is outside the paging scroll, we update the title on page switch.
- mRecommendationPageTitle.setText(mCategoryTitles.get(getNextPage()));
+ int nextPage = getNextPage();
+ mRecommendationPageTitle.setText(mCategoryTitles.get(nextPage));
+ mPageSwitchListeners.forEach(listener -> listener.accept(nextPage));
super.notifyPageSwitchListener(prevPage);
- requestLayout();
}
}
@@ -193,30 +283,24 @@
boolean hasMultiplePages = getChildCount() > 0;
if (hasMultiplePages) {
- int finalWidth = MeasureSpec.getSize(widthMeasureSpec);
int desiredHeight = 0;
+ int desiredWidth = Math.round(mAvailableWidth);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
- measureChild(child, widthMeasureSpec, heightMeasureSpec);
- if (mAvailableHeight == Float.MAX_VALUE) {
- // When we are not limited by height, use currentPage's height. This is the case
- // when the paged layout is placed in a scrollable container. We cannot use
- // height
- // of tallest child in such case, as it will display a scrollbar even for
- // smaller
- // pages that don't have more content.
- if (i == mCurrentPage) {
- int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
- desiredHeight = Math.max(parentHeight, child.getMeasuredHeight());
- }
- } else {
- // Use height of tallest child when we are limited to a certain height.
- desiredHeight = Math.max(desiredHeight, child.getMeasuredHeight());
- }
+ // Measure children based on available height and width.
+ measureChild(child,
+ MeasureSpec.makeMeasureSpec(desiredWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(Math.round(mAvailableHeight),
+ MeasureSpec.AT_MOST));
+ // Use height of tallest child as we have limited height.
+ int childHeight = child.getMeasuredHeight();
+ desiredHeight = Math.max(desiredHeight, childHeight);
}
int finalHeight = resolveSizeAndState(desiredHeight, heightMeasureSpec, 0);
+ int finalWidth = resolveSizeAndState(desiredWidth, widthMeasureSpec, 0);
+
setMeasuredDimension(finalWidth, finalHeight);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -224,18 +308,25 @@
}
/**
- * Groups the provided recommendations into rows and displays them in a table if at least one
- * fits.
- * <p>Returns false if none of the recommendations could fit.</p>
+ * Groups the provided recommendations into rows and displays ones that fit in a table.
+ * <p>Returns the set of widgets that could fit.</p>
*/
- private boolean maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
+ private Set<ComponentName> maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
DeviceProfile deviceProfile,
final @Px int availableWidth, final @Px int cellPadding) {
+ List<WidgetItem> filteredRecommendedWidgets = recommendedWidgets;
+ // Show only those widgets that were displayed when user first opened the picker.
+ if (!mDisplayedWidgets.isEmpty()) {
+ filteredRecommendedWidgets = recommendedWidgets.stream().filter(
+ w -> mDisplayedWidgets.contains(w.componentName)).toList();
+ }
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
+ // Since we are limited by space, we don't sort recommendations - to show most relevant
+ // (if possible).
List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
- recommendedWidgets,
+ filteredRecommendedWidgets,
context,
deviceProfile,
availableWidth,
@@ -249,13 +340,17 @@
recommendationsTable.setWidgetCellOnClickListener(mWidgetCellOnClickListener);
recommendationsTable.setWidgetCellLongClickListener(mWidgetCellOnLongClickListener);
- boolean displayedAtLeastOne = recommendationsTable.setRecommendedWidgets(rows,
+ List<ArrayList<WidgetItem>> displayedItems = recommendationsTable.setRecommendedWidgets(
+ rows,
deviceProfile, mAvailableHeight);
- if (displayedAtLeastOne) {
+
+ if (!displayedItems.isEmpty()) {
addView(recommendationsTable);
}
- return displayedAtLeastOne;
+ return displayedItems.stream().flatMap(
+ items -> items.stream().map(w -> w.componentName))
+ .collect(Collectors.toSet());
}
/** Returns location of a widget cell for displaying the "touch and hold" education tip. */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index f5742af..9929892 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -15,12 +15,8 @@
*/
package com.android.launcher3.widget.picker;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_DIALOG_SEEN;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -29,7 +25,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.os.Build;
+import android.os.Bundle;
import android.os.Parcelable;
import android.os.Process;
import android.os.UserHandle;
@@ -41,6 +37,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.animation.AnimationUtils;
@@ -48,12 +45,10 @@
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
-import android.window.BackEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
-import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.RecyclerView;
@@ -61,19 +56,17 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.model.UserManagerState;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
import com.android.launcher3.views.StickyHeaderLayout;
-import com.android.launcher3.views.WidgetsEduView;
import com.android.launcher3.widget.BaseWidgetSheet;
+import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.search.SearchModeListener;
import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
@@ -81,7 +74,9 @@
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.IntStream;
@@ -93,13 +88,14 @@
WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
private static final long FADE_IN_DURATION = 150;
- private static final long EDUCATION_TIP_DELAY_MS = 200;
- private static final long EDUCATION_DIALOG_DELAY_MS = 500;
// The widget recommendation table can easily take over the entire screen on devices with small
// resolution or landscape on phone. This ratio defines the max percentage of content area that
- // the table can display.
- private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
+ // the table can display with respect to bottom sheet's height.
+ private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.45f;
+ private static final String RECOMMENDATIONS_SAVED_STATE_KEY =
+ "widgetsFullSheet:mRecommendationsCurrentPage";
+ private static final String SUPER_SAVED_STATE_KEY = "widgetsFullSheet:superHierarchyState";
private final UserCache mUserCache;
private final UserManagerState mUserManagerState = new UserManagerState();
private final UserHandle mCurrentUser = Process.myUserHandle();
@@ -107,37 +103,13 @@
entry -> mCurrentUser.equals(entry.mPkgItem.user);
private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter;
protected final boolean mHasWorkProfile;
- protected boolean mHasRecommendedWidgets;
+ // Number of recommendations displayed
+ protected int mRecommendedWidgetsCount;
+ private List<WidgetItem> mRecommendedWidgets = new ArrayList<>();
+ private Map<WidgetRecommendationCategory, List<WidgetItem>> mRecommendedWidgetsMap =
+ new HashMap<>();
+ protected int mRecommendationsCurrentPage = 0;
protected final SparseArray<AdapterHolder> mAdapters = new SparseArray();
- @Nullable private ArrowTipView mLatestEducationalTip;
- private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
- new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- if (hasSeenEducationTip()) {
- removeOnLayoutChangeListener(this);
- return;
- }
-
- // Widgets are loaded asynchronously, We are adding a delay because we only want
- // to show the tip when the widget preview has finished loading and rendering in
- // this view.
- removeCallbacks(mShowEducationTipTask);
- postDelayed(mShowEducationTipTask, EDUCATION_TIP_DELAY_MS);
- }
- };
-
- private final Runnable mShowEducationTipTask = () -> {
- if (hasSeenEducationTip()) {
- removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
- return;
- }
- mLatestEducationalTip = showEducationTipOnViewIfPossible(getViewToShowEducationTip());
- if (mLatestEducationalTip != null) {
- removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
- }
- };
private final OnAttachStateChangeListener mBindScrollbarInSearchMode =
new OnAttachStateChangeListener() {
@@ -155,14 +127,19 @@
}
};
- @Px private final int mTabsHeight;
+ @Px
+ private final int mTabsHeight;
- @Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
- @Nullable private WidgetsRecyclerView mCurrentTouchEventRecyclerView;
- @Nullable PersonalWorkPagedView mViewPager;
+ @Nullable
+ private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
+ @Nullable
+ private WidgetsRecyclerView mCurrentTouchEventRecyclerView;
+ @Nullable
+ PersonalWorkPagedView mViewPager;
private boolean mIsInSearchMode;
private boolean mIsNoWidgetsViewNeeded;
- @Px protected int mMaxSpanPerRow;
+ @Px
+ protected int mMaxSpanPerRow;
protected DeviceProfile mDeviceProfile;
protected TextView mNoWidgetsView;
@@ -226,15 +203,17 @@
R.id.widget_recommendations_container);
mWidgetRecommendationsView = mSearchScrollView.findViewById(
R.id.widget_recommendations_view);
+ // To save the currently displayed page, so that, it can be requested when rebinding
+ // recommendations with different size constraints.
+ mWidgetRecommendationsView.addPageSwitchListener(
+ newPage -> mRecommendationsCurrentPage = newPage);
mWidgetRecommendationsView.initParentViews(mWidgetRecommendationsContainer);
mWidgetRecommendationsView.setWidgetCellLongClickListener(this);
mWidgetRecommendationsView.setWidgetCellOnClickListener(this);
mHeaderTitle = mSearchScrollView.findViewById(R.id.title);
- onRecommendedWidgetsBound();
onWidgetsBound();
- setUpEducationViewsIfNeeded();
}
protected void setupViews() {
@@ -291,13 +270,6 @@
attachScrollbarToRecyclerView(currentRecyclerView);
}
- @Override
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- public void onBackProgressed(@NonNull BackEvent backEvent) {
- super.onBackProgressed(backEvent);
- mFastScroller.setVisibility(backEvent.getProgress() > 0 ? View.INVISIBLE : View.VISIBLE);
- }
-
private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
recyclerView.bindFastScrollbar(mFastScroller);
if (mCurrentWidgetsRecyclerView != recyclerView) {
@@ -358,9 +330,7 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- LauncherAppState.getInstance(mActivityContext).getModel()
- .refreshAndBindWidgetsAndShortcuts(null);
- onRecommendedWidgetsBound();
+ onWidgetsBound();
}
@Override
@@ -541,6 +511,14 @@
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);
setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
if (mHasWorkProfile) {
mViewPager.snapToPage(AdapterHolder.PRIMARY);
@@ -588,44 +566,46 @@
}
if (enableCategorizedWidgetSuggestions()) {
- mHasRecommendedWidgets = mWidgetRecommendationsView.setRecommendations(
- mActivityContext.getPopupDataProvider().getCategorizedRecommendedWidgets(),
+ // We avoid applying new recommendations when some are already displayed.
+ if (mRecommendedWidgetsMap.isEmpty()) {
+ mRecommendedWidgetsMap =
+ mActivityContext.getPopupDataProvider().getCategorizedRecommendedWidgets();
+ }
+ mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
+ mRecommendedWidgetsMap,
mDeviceProfile,
/* availableHeight= */ getMaxAvailableHeightForRecommendations(),
/* availableWidth= */ mMaxSpanPerRow,
- /* cellPadding= */ mWidgetCellHorizontalPadding
+ /* cellPadding= */ mWidgetCellHorizontalPadding,
+ /* requestedPage= */ mRecommendationsCurrentPage
);
} else {
- mHasRecommendedWidgets = mWidgetRecommendationsView.setRecommendations(
- mActivityContext.getPopupDataProvider().getRecommendedWidgets(),
+ if (mRecommendedWidgets.isEmpty()) {
+ mRecommendedWidgets =
+ mActivityContext.getPopupDataProvider().getRecommendedWidgets();
+ }
+ mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
+ mRecommendedWidgets,
mDeviceProfile,
/* availableHeight= */ getMaxAvailableHeightForRecommendations(),
/* availableWidth= */ mMaxSpanPerRow,
/* cellPadding= */ mWidgetCellHorizontalPadding
);
}
- mWidgetRecommendationsContainer.setVisibility(mHasRecommendedWidgets ? VISIBLE : GONE);
+ mWidgetRecommendationsContainer.setVisibility(
+ mRecommendedWidgetsCount > 0 ? VISIBLE : GONE);
}
@Px
- private float getMaxAvailableHeightForRecommendations() {
- float noWidgetsViewHeight = 0;
- if (mIsNoWidgetsViewNeeded) {
- // Make sure recommended section leaves enough space for noWidgetsView.
- Rect noWidgetsViewTextBounds = new Rect();
- mNoWidgetsView.getPaint()
- .getTextBounds(mNoWidgetsView.getText().toString(), /* start= */ 0,
- mNoWidgetsView.getText().length(), noWidgetsViewTextBounds);
- noWidgetsViewHeight = noWidgetsViewTextBounds.height();
+ protected float getMaxAvailableHeightForRecommendations() {
+ // There isn't enough space to show recommendations in landscape orientation on phones with
+ // a full sheet design. Tablets use a two pane picker.
+ if (mDeviceProfile.isLandscape) {
+ return 0f;
}
- if (!isTwoPane()) {
- doMeasure(
- makeMeasureSpec(mActivityContext.getDeviceProfile().availableWidthPx,
- MeasureSpec.EXACTLY),
- makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
- MeasureSpec.EXACTLY));
- }
- return getMaxTableHeight(noWidgetsViewHeight);
+
+ return (mDeviceProfile.heightPx - mDeviceProfile.bottomSheetTopPadding)
+ * RECOMMENDATION_TABLE_HEIGHT_RATIO;
}
/** b/209579563: "Widgets" header should be focused first. */
@@ -634,14 +614,6 @@
return mHeaderTitle;
}
- @Px
- protected float getMaxTableHeight(@Px float noWidgetsViewHeight) {
- return (mContent.getMeasuredHeight()
- - mTabsHeight - getHeaderViewHeight()
- - noWidgetsViewHeight)
- * RECOMMENDATION_TABLE_HEIGHT_RATIO;
- }
-
private void open(boolean animate) {
if (animate) {
if (getPopupContainer().getInsets().bottom > 0) {
@@ -710,6 +682,29 @@
return sheet;
}
+ @Override
+ public void saveHierarchyState(SparseArray<Parcelable> sparseArray) {
+ Bundle bundle = new Bundle();
+ // With widget picker open, when we open shade to switch theme, Launcher re-creates the
+ // picker and calls save/restore hierarchy state. We save the state of recommendations
+ // across those updates.
+ bundle.putInt(RECOMMENDATIONS_SAVED_STATE_KEY, mRecommendationsCurrentPage);
+ mWidgetRecommendationsView.saveState(bundle);
+ SparseArray<Parcelable> superState = new SparseArray<>();
+ super.saveHierarchyState(superState);
+ bundle.putSparseParcelableArray(SUPER_SAVED_STATE_KEY, superState);
+ sparseArray.put(0, bundle);
+ }
+
+ @Override
+ public void restoreHierarchyState(SparseArray<Parcelable> sparseArray) {
+ Bundle state = (Bundle) sparseArray.get(0);
+ mRecommendationsCurrentPage = state.getInt(
+ RECOMMENDATIONS_SAVED_STATE_KEY, /*defaultValue=*/0);
+ mWidgetRecommendationsView.restoreState(state);
+ super.restoreHierarchyState(state.getSparseParcelableArray(SUPER_SAVED_STATE_KEY));
+ }
+
private static int getWidgetSheetId(BaseActivity activity) {
boolean isTwoPane = (activity.getDeviceProfile().isTablet
// Enables two pane picker for tablets in all orientations when the
@@ -776,17 +771,12 @@
@Override
public void addHintCloseAnim(
float distanceToMove, Interpolator interpolator, PendingAnimation target) {
- target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
- target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
+ target.addAnimatedFloat(mSwipeToDismissProgress, 0f, 1f, interpolator);
}
@Override
protected void onCloseComplete() {
super.onCloseComplete();
- removeCallbacks(mShowEducationTipTask);
- if (mLatestEducationalTip != null) {
- mLatestEducationalTip.close(true);
- }
AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
}
@@ -797,8 +787,8 @@
}
/** private the height, in pixel, + the vertical margins of a given view. */
- private static int measureHeightWithVerticalMargins(View view) {
- if (view.getVisibility() != VISIBLE) {
+ protected static int measureHeightWithVerticalMargins(View view) {
+ if (view == null || view.getVisibility() != VISIBLE) {
return 0;
}
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
@@ -833,6 +823,7 @@
saveHierarchyState(widgetsState);
handleClose(false);
WidgetsFullSheet sheet = show(BaseActivity.fromContext(getContext()), false);
+ sheet.restoreRecommendations(mRecommendedWidgets, mRecommendedWidgetsMap);
sheet.restoreHierarchyState(widgetsState);
sheet.restorePreviousAdapterHolderType(getCurrentAdapterHolderType());
} else if (!isTwoPane()) {
@@ -843,6 +834,12 @@
mDeviceProfile = dp;
}
+ private void restoreRecommendations(List<WidgetItem> recommendedWidgets,
+ Map<WidgetRecommendationCategory, List<WidgetItem>> recommendedWidgetsMap) {
+ mRecommendedWidgets = recommendedWidgets;
+ mRecommendedWidgetsMap = recommendedWidgetsMap;
+ }
+
/**
* Indicates if layout should be re-created on device profile change - so that a different
* layout can be displayed.
@@ -862,11 +859,23 @@
return isFoldUnFold || useDifferentLayoutOnOrientationChange;
}
+ /**
+ * In widget search mode, we should scale down content inside widget bottom sheet, rather
+ * than the whole bottom sheet, to indicate we will navigate back within the widget
+ * bottom sheet.
+ */
+ @Override
+ public boolean shouldAnimateContentViewInBackSwipe() {
+ return mIsInSearchMode;
+ }
+
@Override
public void onBackInvoked() {
if (mIsInSearchMode) {
mSearchBar.reset();
- animateSlideInViewToNoScale();
+ // Posting animation to next frame will let widget sheet finish updating UI first, and
+ // make animation smoother.
+ post(this::animateSwipeToDismissProgressToStart);
} else {
super.onBackInvoked();
}
@@ -881,7 +890,8 @@
}
}
- @Nullable private View getViewToShowEducationTip() {
+ @Nullable
+ private View getViewToShowEducationTip() {
if (mWidgetRecommendationsContainer.getVisibility() == VISIBLE) {
return mWidgetRecommendationsView.getViewForEducationTip();
}
@@ -906,36 +916,6 @@
return null;
}
- /** Shows education dialog for widgets. */
- private WidgetsEduView showEducationDialog() {
- LauncherPrefs.get(getContext()).put(WIDGETS_EDUCATION_DIALOG_SEEN, true);
- return WidgetsEduView.showEducationDialog(mActivityContext);
- }
-
- /** Returns {@code true} if education dialog has previously been shown. */
- protected boolean hasSeenEducationDialog() {
- return LauncherPrefs.get(getContext()).get(WIDGETS_EDUCATION_DIALOG_SEEN)
- || Utilities.isRunningInTestHarness();
- }
-
- protected void setUpEducationViewsIfNeeded() {
- if (!hasSeenEducationDialog()) {
- postDelayed(() -> {
- WidgetsEduView eduDialog = showEducationDialog();
- eduDialog.addOnCloseListener(() -> {
- if (!hasSeenEducationTip()) {
- addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
- // Call #requestLayout() to trigger layout change listener in order to show
- // arrow tip immediately if there is a widget to show it on.
- requestLayout();
- }
- });
- }, EDUCATION_DIALOG_DELAY_MS);
- } else if (!hasSeenEducationTip()) {
- addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
- }
- }
-
protected boolean isTwoPane() {
return false;
}
@@ -953,6 +933,60 @@
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
}
+ @Override
+ protected int getHeaderTopClip(@NonNull WidgetCell cell) {
+ StickyHeaderLayout header = findViewById(R.id.search_and_recommendations_container);
+ if (header == null) {
+ return 0;
+ }
+ Rect cellRect = new Rect();
+ boolean cellIsPartiallyVisible = cell.getGlobalVisibleRect(cellRect);
+ if (cellIsPartiallyVisible) {
+ Rect occludingRect = new Rect();
+ for (View headerChild : header.getStickyChildren()) {
+ Rect childRect = new Rect();
+ boolean childVisible = headerChild.getGlobalVisibleRect(childRect);
+ if (childVisible && childRect.intersect(cellRect)) {
+ occludingRect.union(childRect);
+ }
+ }
+ if (!occludingRect.isEmpty() && cellRect.top < occludingRect.bottom) {
+ return occludingRect.bottom - cellRect.top;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ protected void scrollCellContainerByY(WidgetCell wc, int scrollByY) {
+ for (ViewParent parent = wc.getParent(); parent != null; parent = parent.getParent()) {
+ if (parent instanceof WidgetsRecyclerView recyclerView) {
+ // Scrollable container for main widget list.
+ recyclerView.smoothScrollBy(0, scrollByY);
+ return;
+ } else if (parent instanceof StickyHeaderLayout header) {
+ // Scrollable container for recommendations. We still scroll on the recycler (even
+ // though the recommendations are not in the recycler view) because the
+ // StickyHeaderLayout scroll is connected to the currently visible recycler view.
+ WidgetsRecyclerView recyclerView = findVisibleRecyclerView();
+ if (recyclerView != null) {
+ recyclerView.smoothScrollBy(0, scrollByY);
+ }
+ return;
+ } else if (parent == this) {
+ return;
+ }
+ }
+ }
+
+ @Nullable
+ private WidgetsRecyclerView findVisibleRecyclerView() {
+ if (mViewPager != null) {
+ return (WidgetsRecyclerView) mViewPager.getPageAt(mViewPager.getCurrentPage());
+ }
+ return findViewById(R.id.primary_widgets_list_view);
+ }
+
/** A holder class for holding adapters & their corresponding recycler view. */
final class AdapterHolder {
static final int PRIMARY = 0;
@@ -987,10 +1021,7 @@
default:
break;
}
- mWidgetsListItemAnimator = new DefaultItemAnimator();
- // Disable change animations because it disrupts the item focus upon adapter item
- // change.
- mWidgetsListItemAnimator.setSupportsChangeAnimations(false);
+ mWidgetsListItemAnimator = new WidgetsListItemAnimator();
}
private int getEmptySpaceHeight() {
@@ -1004,7 +1035,7 @@
mWidgetsRecyclerView.setClipChildren(false);
mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
mWidgetsRecyclerView.bindFastScrollbar(mFastScroller);
- mWidgetsRecyclerView.setItemAnimator(mWidgetsListItemAnimator);
+ mWidgetsRecyclerView.setItemAnimator(isTwoPane() ? null : mWidgetsListItemAnimator);
mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
if (!isTwoPane()) {
mWidgetsRecyclerView.setEdgeEffectFactory(
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index d373a3b..d164dd0 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.widget.picker;
+import static android.animation.ValueAnimator.areAnimatorsEnabled;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -201,7 +203,7 @@
public void reapplyItemInfo(ItemInfoWithIcon info) {
if (getTag() == info) {
mIconLoadRequest = null;
- mEnableIconUpdateAnimation = true;
+ mEnableIconUpdateAnimation = areAnimatorsEnabled();
// Optimization: Starting in N, pre-uploads the bitmap to RenderThread.
info.bitmap.icon.prepareToDraw();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java b/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java
new file mode 100644
index 0000000..854700f
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker;
+
+import static com.android.launcher3.widget.picker.WidgetsListAdapter.VIEW_TYPE_WIDGETS_LIST;
+
+import androidx.recyclerview.widget.DefaultItemAnimator;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class WidgetsListItemAnimator extends DefaultItemAnimator {
+ public static final int CHANGE_DURATION_MS = 90;
+ public static final int MOVE_DURATION_MS = 90;
+ public static final int ADD_DURATION_MS = 120;
+
+ public WidgetsListItemAnimator() {
+ super();
+
+ // Disable change animations because it disrupts the item focus upon adapter item
+ // change.
+ setSupportsChangeAnimations(false);
+ // Make the moves a bit faster, so that the amount of time for which user sees the
+ // bottom-sheet background before "add" animation starts is less making it smoother.
+ setChangeDuration(CHANGE_DURATION_MS);
+ setMoveDuration(MOVE_DURATION_MS);
+ setAddDuration(ADD_DURATION_MS);
+ }
+ @Override
+ public boolean animateChange(RecyclerView.ViewHolder oldHolder,
+ RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft,
+ int toTop) {
+ // As we expand an item, the content / widgets list that appears (with add
+ // event) also gets change events as its previews load asynchronously. The
+ // super implementation of animateChange cancels the animations on it - breaking
+ // the "add animation". Instead, here, we skip "change" animation for content
+ // list - because we want it to either appear or disappear. And, the previews
+ // themselves have their own animation when loaded, so, we don't need change
+ // animations for them anyway. Below, we do-nothing.
+ if (oldHolder.getItemViewType() == VIEW_TYPE_WIDGETS_LIST) {
+ dispatchChangeStarting(oldHolder, true);
+ dispatchChangeFinished(oldHolder, true);
+ return true;
+ }
+ return super.animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft,
+ toTop);
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index ef3ccf0..45d733a 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -15,7 +15,10 @@
*/
package com.android.launcher3.widget.picker;
-import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
+import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.CHANGE_DURATION_MS;
+import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.MOVE_DURATION_MS;
+
+import static android.animation.ValueAnimator.areAnimatorsEnabled;
import android.content.Context;
import android.graphics.Bitmap;
@@ -28,7 +31,6 @@
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.TableLayout;
-import android.widget.TableRow;
import androidx.annotation.NonNull;
import androidx.annotation.Px;
@@ -38,6 +40,7 @@
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.WidgetTableRow;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.util.WidgetsTableUtils;
@@ -113,17 +116,15 @@
for (int i = 0; i < widgetItemsTable.size(); i++) {
List<WidgetItem> widgetItemsPerRow = widgetItemsTable.get(i);
for (int j = 0; j < widgetItemsPerRow.size(); j++) {
- TableRow row = (TableRow) table.getChildAt(i);
+ WidgetTableRow row = (WidgetTableRow) table.getChildAt(i);
row.setVisibility(View.VISIBLE);
WidgetCell widget = (WidgetCell) row.getChildAt(j);
widget.clear();
+ widget.addPreviewReadyListener(row);
WidgetItem widgetItem = widgetItemsPerRow.get(j);
widget.setVisibility(View.VISIBLE);
- // When preview loads, notify adapter to rebind the item and possibly animate
- widget.applyFromCellItem(widgetItem, 1f,
- bitmap -> holder.onPreviewLoaded(Pair.create(widgetItem, bitmap)),
- holder.previewCache.get(widgetItem));
+ widget.applyFromCellItem(widgetItem);
widget.requestLayout();
}
}
@@ -145,20 +146,19 @@
for (int i = 0; i < widgetItemsTable.size(); i++) {
List<WidgetItem> widgetItems = widgetItemsTable.get(i);
- TableRow tableRow;
+ WidgetTableRow tableRow;
if (i < table.getChildCount()) {
- tableRow = (TableRow) table.getChildAt(i);
+ tableRow = (WidgetTableRow) table.getChildAt(i);
} else {
- tableRow = new TableRow(table.getContext());
- if (enableCategorizedWidgetSuggestions()) {
- // Vertically center align items, so that even if they don't fill bounds, they
- // can look organized when placed together in a row.
- tableRow.setGravity(Gravity.CENTER_VERTICAL);
- } else {
- tableRow.setGravity(Gravity.TOP);
- }
+ tableRow = new WidgetTableRow(table.getContext());
+ tableRow.setGravity(Gravity.TOP);
table.addView(tableRow);
}
+ // Pass resize delay to let the "move" and "change" animations run before resizing the
+ // row.
+ tableRow.setupRow(widgetItems.size(),
+ /*resizeDelayMs=*/
+ areAnimatorsEnabled() ? (CHANGE_DURATION_MS + MOVE_DURATION_MS) : 0);
if (tableRow.getChildCount() > widgetItems.size()) {
for (int j = widgetItems.size(); j < tableRow.getChildCount(); j++) {
tableRow.getChildAt(j).setVisibility(View.GONE);
@@ -168,6 +168,8 @@
WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
R.layout.widget_cell, tableRow, false);
// set up touch.
+ widget.setOnClickListener(mIconClickListener);
+ widget.addPreviewReadyListener(tableRow);
View preview = widget.findViewById(R.id.widget_preview_container);
preview.setOnClickListener(mIconClickListener);
preview.setOnLongClickListener(mIconLongClickListener);
@@ -183,7 +185,7 @@
int numOfRows = holder.tableContainer.getChildCount();
holder.previewCache.clear();
for (int i = 0; i < numOfRows; i++) {
- TableRow tableRow = (TableRow) holder.tableContainer.getChildAt(i);
+ WidgetTableRow tableRow = (WidgetTableRow) holder.tableContainer.getChildAt(i);
int numOfCols = tableRow.getChildCount();
for (int j = 0; j < numOfCols; j++) {
WidgetCell widget = (WidgetCell) tableRow.getChildAt(j);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 12564f4..1ed3d88 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -17,39 +17,37 @@
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
+import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_SIZE_COMPARATOR;
+
+import static java.lang.Math.max;
import android.content.Context;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Size;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.TableLayout;
-import android.widget.TableRow;
import androidx.annotation.Nullable;
+import androidx.annotation.Px;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.widget.WidgetCell;
-import com.android.launcher3.widget.util.WidgetSizes;
+import com.android.launcher3.widget.WidgetTableRow;
+import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
import java.util.ArrayList;
import java.util.List;
/** A {@link TableLayout} for showing recommended widgets. */
public final class WidgetsRecommendationTableLayout extends TableLayout {
- private static final String TAG = "WidgetsRecommendationTableLayout";
- private static final float DOWN_SCALE_RATIO = 0.9f;
- private static final float MAX_DOWN_SCALE_RATIO = 0.5f;
private final float mWidgetsRecommendationTableVerticalPadding;
private final float mWidgetCellVerticalPadding;
private final float mWidgetCellTextViewsHeight;
- private float mRecommendationTableMaxHeight = Float.MAX_VALUE;
@Nullable private OnLongClickListener mWidgetCellOnLongClickListener;
@Nullable private OnClickListener mWidgetCellOnClickListener;
@@ -59,12 +57,13 @@
public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- // There are 1 row for title, 1 row for dimension and 2 rows for description.
+ // There are 1 row for title, 1 row for dimension and max 3 rows for description.
mWidgetsRecommendationTableVerticalPadding = 2 * getResources()
.getDimensionPixelSize(R.dimen.widget_recommendations_table_vertical_padding);
mWidgetCellVerticalPadding = 2 * getResources()
.getDimensionPixelSize(R.dimen.widget_cell_vertical_padding);
- mWidgetCellTextViewsHeight = 4 * getResources().getDimension(R.dimen.widget_cell_font_size);
+ mWidgetCellTextViewsHeight =
+ getResources().getDimension(R.dimen.widget_cell_title_line_height);
}
/** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
@@ -82,47 +81,39 @@
* desired {@code recommendationTableMaxHeight}.
*
* <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
- * last row from the {@code recommendedWidgets} until it fits or only one row left. If the only
- * row still doesn't fit, we scale down the preview image.
+ * last row from the {@code recommendedWidgets} until it fits or only one row left.
*
- * <p>Returns {@code false} if none of the widgets could fit</p>
+ * <p>Returns the list of widgets that could fit</p>
*/
- public boolean setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
- DeviceProfile deviceProfile,
- float recommendationTableMaxHeight) {
- mRecommendationTableMaxHeight = recommendationTableMaxHeight;
- RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
- deviceProfile,
- recommendedWidgets);
- bindData(data);
- return !data.mRecommendationTable.isEmpty();
+ public List<ArrayList<WidgetItem>> setRecommendedWidgets(
+ List<ArrayList<WidgetItem>> recommendedWidgets,
+ DeviceProfile deviceProfile, float recommendationTableMaxHeight) {
+ List<ArrayList<WidgetItem>> rows = selectRowsThatFitInAvailableHeight(recommendedWidgets,
+ recommendationTableMaxHeight, deviceProfile);
+ bindData(rows);
+ return rows;
}
- private void bindData(RecommendationTableData data) {
- if (data.mRecommendationTable.isEmpty()) {
+ private void bindData(List<ArrayList<WidgetItem>> recommendationTable) {
+ if (recommendationTable.isEmpty()) {
setVisibility(GONE);
return;
}
removeAllViews();
- for (int i = 0; i < data.mRecommendationTable.size(); i++) {
- List<WidgetItem> widgetItems = data.mRecommendationTable.get(i);
- TableRow tableRow = new TableRow(getContext());
- if (enableCategorizedWidgetSuggestions()) {
- // Vertically center align items, so that even if they don't fill bounds, they can
- // look organized when placed together in a row.
- tableRow.setGravity(Gravity.CENTER_VERTICAL);
- } else {
- tableRow.setGravity(Gravity.TOP);
- }
+ for (int i = 0; i < recommendationTable.size(); i++) {
+ List<WidgetItem> widgetItems = recommendationTable.get(i);
+ WidgetTableRow tableRow = new WidgetTableRow(getContext());
+ tableRow.setupRow(widgetItems.size(), /*resizeDelayMs=*/ 0);
+ tableRow.setGravity(Gravity.TOP);
for (WidgetItem widgetItem : widgetItems) {
WidgetCell widgetCell = addItemCell(tableRow);
- widgetCell.applyFromCellItem(widgetItem, data.mPreviewScale);
+ widgetCell.applyFromCellItem(widgetItem);
widgetCell.showAppIconInWidgetTitle(true);
- widgetCell.showBadge();
if (enableCategorizedWidgetSuggestions()) {
widgetCell.showDescription(false);
+ widgetCell.showDimensions(false);
}
}
addView(tableRow);
@@ -130,9 +121,11 @@
setVisibility(VISIBLE);
}
- private WidgetCell addItemCell(ViewGroup parent) {
+ private WidgetCell addItemCell(WidgetTableRow parent) {
WidgetCell widget = (WidgetCell) LayoutInflater.from(
getContext()).inflate(R.layout.widget_cell, parent, false);
+ widget.addPreviewReadyListener(parent);
+ widget.setOnClickListener(mWidgetCellOnClickListener);
View previewContainer = widget.findViewById(R.id.widget_preview_container);
previewContainer.setOnClickListener(mWidgetCellOnClickListener);
@@ -144,58 +137,32 @@
return widget;
}
- private RecommendationTableData fitRecommendedWidgetsToTableSpace(
- float previewScale,
- DeviceProfile deviceProfile,
- List<ArrayList<WidgetItem>> recommendedWidgetsInTable) {
- if (previewScale < MAX_DOWN_SCALE_RATIO) {
- Log.w(TAG, "Hide recommended widgets. Can't down scale previews to " + previewScale);
- return new RecommendationTableData(List.of(), previewScale);
- }
+ private List<ArrayList<WidgetItem>> selectRowsThatFitInAvailableHeight(
+ List<ArrayList<WidgetItem>> recommendedWidgets, @Px float recommendationTableMaxHeight,
+ DeviceProfile deviceProfile) {
+ List<ArrayList<WidgetItem>> filteredRows = new ArrayList<>();
// A naive estimation of the widgets recommendation table height without inflation.
float totalHeight = mWidgetsRecommendationTableVerticalPadding;
- for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
- List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);
+
+ for (int i = 0; i < recommendedWidgets.size(); i++) {
+ List<WidgetItem> widgetItems = recommendedWidgets.get(i);
float rowHeight = 0;
for (int j = 0; j < widgetItems.size(); j++) {
WidgetItem widgetItem = widgetItems.get(j);
- Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile,
- widgetItem);
- float previewHeight = widgetSize.getHeight() * previewScale;
- rowHeight = Math.max(rowHeight,
- previewHeight + mWidgetCellTextViewsHeight + mWidgetCellVerticalPadding);
+ WidgetPreviewContainerSize previewContainerSize =
+ WidgetPreviewContainerSize.Companion.forItem(widgetItem, deviceProfile);
+ float widgetItemHeight = getWidgetSizePx(deviceProfile, previewContainerSize.spanX,
+ previewContainerSize.spanY).getHeight();
+ rowHeight = max(rowHeight,
+ widgetItemHeight + mWidgetCellTextViewsHeight + mWidgetCellVerticalPadding);
}
- totalHeight += rowHeight;
+ if (totalHeight + rowHeight <= recommendationTableMaxHeight) {
+ totalHeight += rowHeight;
+ filteredRows.add(new ArrayList<>(widgetItems));
+ }
}
- if (totalHeight < mRecommendationTableMaxHeight) {
- return new RecommendationTableData(recommendedWidgetsInTable, previewScale);
- }
-
- if (recommendedWidgetsInTable.size() > 1) {
- // We don't want to scale down widgets preview unless we really need to. Reduce the
- // num of row by 1 to see if it fits.
- return fitRecommendedWidgetsToTableSpace(
- previewScale,
- deviceProfile,
- recommendedWidgetsInTable.subList(/* fromIndex= */0,
- /* toIndex= */recommendedWidgetsInTable.size() - 1));
- }
-
- float nextPreviewScale = previewScale * DOWN_SCALE_RATIO;
- return fitRecommendedWidgetsToTableSpace(nextPreviewScale, deviceProfile,
- recommendedWidgetsInTable);
- }
-
- /** Data class for the widgets recommendation table and widgets preview scaling. */
- private class RecommendationTableData {
- private final List<ArrayList<WidgetItem>> mRecommendationTable;
- private final float mPreviewScale;
-
- RecommendationTableData(List<ArrayList<WidgetItem>> recommendationTable,
- float previewScale) {
- mRecommendationTable = recommendationTable;
- mPreviewScale = previewScale;
- }
+ // Perform re-ordering once we have filtered out recommendations that fit.
+ return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 698e764..a47818f 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -73,7 +73,7 @@
* Maps the touch (from 0..1) to the adapter position that should be visible.
*/
@Override
- public String scrollToPositionAtProgress(float touchFraction) {
+ public CharSequence scrollToPositionAtProgress(float touchFraction) {
// Skip early if widgets are not bound.
if (isModelNotReady()) {
return "";
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 165b2fe..5d71db6 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -17,28 +17,36 @@
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
+import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER;
+import static com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER;
+import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree;
+import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree;
import android.content.Context;
-import android.graphics.Outline;
import android.graphics.Rect;
import android.os.Process;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewOutlineProvider;
+import android.view.ViewGroup;
+import android.view.ViewParent;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.Px;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
@@ -57,30 +65,22 @@
private static final int MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 395;
private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
+ // This ratio defines the max percentage of content area that the recommendations can display
+ // with respect to the bottom sheet's height.
+ private static final float RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE = 0.70f;
private FrameLayout mSuggestedWidgetsContainer;
private WidgetsListHeader mSuggestedWidgetsHeader;
private PackageUserKey mSuggestedWidgetsPackageUserKey;
+ private View mPrimaryWidgetListView;
private LinearLayout mRightPane;
private ScrollView mRightPaneScrollView;
private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
- private int mActivePage = -1;
- private PackageUserKey mSelectedHeader;
- private final ViewOutlineProvider mViewOutlineProviderRightPane = new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(
- 0,
- 0,
- view.getMeasuredWidth(),
- view.getMeasuredHeight() - getResources().getDimensionPixelSize(
- R.dimen.widget_list_horizontal_margin_two_pane),
- view.getResources().getDimensionPixelSize(
- R.dimen.widget_list_top_bottom_corner_radius)
- );
- }
- };
+ private boolean mOldIsSwipeToDismissInProgress;
+ private int mActivePage = -1;
+ @Nullable
+ private PackageUserKey mSelectedHeader;
public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -118,22 +118,68 @@
mWidgetRecommendationsView.initParentViews(mWidgetRecommendationsContainer);
mWidgetRecommendationsView.setWidgetCellLongClickListener(this);
mWidgetRecommendationsView.setWidgetCellOnClickListener(this);
+ // To save the currently displayed page, so that, it can be requested when rebinding
+ // recommendations with different size constraints.
+ mWidgetRecommendationsView.addPageSwitchListener(
+ newPage -> mRecommendationsCurrentPage = newPage);
mHeaderTitle = mContent.findViewById(R.id.title);
mRightPane = mContent.findViewById(R.id.right_pane);
- mRightPane.setOutlineProvider(mViewOutlineProviderRightPane);
mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view);
mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
- onRecommendedWidgetsBound();
+ mPrimaryWidgetListView = findViewById(R.id.primary_widgets_list_view);
+ mPrimaryWidgetListView.setOutlineProvider(mViewOutlineProvider);
+ mPrimaryWidgetListView.setClipToOutline(true);
+
onWidgetsBound();
- setUpEducationViewsIfNeeded();
// Set the fast scroller as not visible for two pane layout.
mFastScroller.setVisibility(GONE);
}
@Override
+ protected int getTabletHorizontalMargin(DeviceProfile deviceProfile) {
+ if (enableCategorizedWidgetSuggestions()) {
+ // two pane picker is full width for fold as well as tablet.
+ return getResources().getDimensionPixelSize(
+ R.dimen.widget_picker_two_panels_left_right_margin);
+ }
+ if (deviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {
+ // enableUnfoldedTwoPanePicker made two pane picker full-width for fold only.
+ return getResources().getDimensionPixelSize(
+ R.dimen.widget_picker_two_panels_left_right_margin);
+ }
+ if (deviceProfile.isLandscape && !deviceProfile.isTwoPanels) {
+ // non-fold tablet landscape margins (ag/22163531)
+ return getResources().getDimensionPixelSize(
+ R.dimen.widget_picker_landscape_tablet_left_right_margin);
+ }
+ return deviceProfile.allAppsLeftRightMargin;
+ }
+
+ @Override
+ protected void onUserSwipeToDismissProgressChanged() {
+ super.onUserSwipeToDismissProgressChanged();
+ boolean isSwipeToDismissInProgress = mSwipeToDismissProgress.value > 0;
+ if (isSwipeToDismissInProgress == mOldIsSwipeToDismissInProgress) {
+ return;
+ }
+ mOldIsSwipeToDismissInProgress = isSwipeToDismissInProgress;
+ if (isSwipeToDismissInProgress) {
+ modifyAttributesOnViewTree(mPrimaryWidgetListView, (ViewParent) mContent,
+ CLIP_CHILDREN_FALSE_MODIFIER);
+ modifyAttributesOnViewTree(mRightPaneScrollView, (ViewParent) mContent,
+ CLIP_CHILDREN_FALSE_MODIFIER, CLIP_TO_PADDING_FALSE_MODIFIER);
+ } else {
+ restoreAttributesOnViewTree(mPrimaryWidgetListView, mContent,
+ CLIP_CHILDREN_FALSE_MODIFIER);
+ restoreAttributesOnViewTree(mRightPaneScrollView, mContent,
+ CLIP_CHILDREN_FALSE_MODIFIER, CLIP_TO_PADDING_FALSE_MODIFIER);
+ }
+ }
+
+ @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed && mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {
@@ -152,22 +198,28 @@
layoutParams.width = 0;
}
layoutParams.weight = layoutParams.width == 0 ? 0.33F : 0;
- leftPane.setLayoutParams(layoutParams);
- requestApplyInsets();
- if (mSelectedHeader != null) {
- if (mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) {
- mSuggestedWidgetsHeader.callOnClick();
- } else {
- getHeaderChangeListener().onHeaderChanged(mSelectedHeader);
+
+ post(() -> {
+ // The following calls all trigger requestLayout, so we post them to avoid
+ // calling requestLayout during a layout pass. This also fixes the related warnings
+ // in logcat.
+ leftPane.setLayoutParams(layoutParams);
+ requestApplyInsets();
+ if (mSelectedHeader != null) {
+ if (mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) {
+ mSuggestedWidgetsHeader.callOnClick();
+ } else {
+ getHeaderChangeListener().onHeaderChanged(mSelectedHeader);
+ }
}
- }
+ });
}
}
@Override
public void onWidgetsBound() {
super.onWidgetsBound();
- if (!mHasRecommendedWidgets && mSelectedHeader == null) {
+ if (mRecommendedWidgetsCount == 0 && mSelectedHeader == null) {
mAdapters.get(mActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
mAdapters.get(mActivePage).mWidgetsRecyclerView.scrollToTop();
}
@@ -177,9 +229,13 @@
public void onRecommendedWidgetsBound() {
super.onRecommendedWidgetsBound();
- if (mSuggestedWidgetsContainer == null && mHasRecommendedWidgets) {
+ if (mSuggestedWidgetsContainer == null && mRecommendedWidgetsCount > 0) {
setupSuggestedWidgets(LayoutInflater.from(getContext()));
mSuggestedWidgetsHeader.callOnClick();
+ } else if (mSelectedHeader != null
+ && mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) {
+ // Reselect widget if we are reloading recommendations while it is currently showing.
+ selectWidgetCell(mWidgetRecommendationsContainer, getLastSelectedWidgetItem());
}
}
@@ -207,10 +263,13 @@
String suggestionsRightPaneTitle = getContext().getString(
R.string.widget_picker_right_pane_accessibility_title, suggestionsHeaderTitle);
packageItemInfo.title = suggestionsHeaderTitle;
+ // Suggestions may update at run time. The widgets count on suggestions doesn't add any
+ // value, so, we don't show the count.
WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create(
packageItemInfo,
- suggestionsHeaderTitle,
- mActivityContext.getPopupDataProvider().getRecommendedWidgets())
+ /*titleSectionName=*/ suggestionsHeaderTitle,
+ /*items=*/ mActivityContext.getPopupDataProvider().getRecommendedWidgets(),
+ /*visibleWidgetsCount=*/ 0)
.withWidgetListShown();
mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry);
@@ -224,6 +283,16 @@
mRightPaneScrollView.setScrollY(0);
mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle);
mSuggestedWidgetsPackageUserKey = PackageUserKey.fromPackageItemInfo(packageItemInfo);
+ final boolean isChangingHeaders = mSelectedHeader == null
+ || !mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey);
+ if (isChangingHeaders) {
+ // If switching from another header, unselect any WidgetCells. This is necessary
+ // because we do not clear/recycle the WidgetCells in the recommendations container
+ // when the header is clicked, only when onRecommendationsBound is called. That
+ // means a WidgetCell in the recommendations container may still be selected from
+ // the last time the recommendations were shown.
+ unselectWidgetCell(mWidgetRecommendationsContainer, getLastSelectedWidgetItem());
+ }
mSelectedHeader = mSuggestedWidgetsPackageUserKey;
});
mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader);
@@ -232,8 +301,15 @@
@Override
@Px
- protected float getMaxTableHeight(@Px float noWidgetsViewHeight) {
- return Float.MAX_VALUE;
+ protected float getMaxAvailableHeightForRecommendations() {
+ if (mRecommendedWidgetsCount > 0) {
+ // If widgets were already selected for display, we show them all on orientation change
+ // in a two pane picker
+ return Float.MAX_VALUE;
+ }
+
+ return (mDeviceProfile.heightPx - mDeviceProfile.bottomSheetTopPadding)
+ * RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE;
}
@Override
@@ -305,6 +381,8 @@
return new HeaderChangeListener() {
@Override
public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) {
+ final boolean isSameHeader = mSelectedHeader != null
+ && mSelectedHeader.equals(selectedHeader);
mSelectedHeader = selectedHeader;
WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
.getSelectedAppWidgets(selectedHeader);
@@ -332,11 +410,20 @@
contentEntryToBind,
ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
Collections.EMPTY_LIST);
+ if (isSameHeader) {
+ // Reselect the last selected widget if we are reloading the same header.
+ selectWidgetCell(widgetsRowViewHolder.tableContainer,
+ getLastSelectedWidgetItem());
+ }
widgetsRowViewHolder.mDataCallback = data -> {
mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
contentEntryToBind,
ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
Collections.singletonList(data));
+ if (isSameHeader) {
+ selectWidgetCell(widgetsRowViewHolder.tableContainer,
+ getLastSelectedWidgetItem());
+ }
};
mRightPane.removeAllViews();
mRightPane.addView(widgetsRowViewHolder.itemView);
@@ -349,6 +436,24 @@
};
}
+ private static void selectWidgetCell(ViewGroup parent, WidgetItem item) {
+ if (parent == null || item == null) return;
+ WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell wc
+ && wc.matchesItem(item));
+ if (cell != null && !cell.isShowingAddButton()) {
+ cell.callOnClick();
+ }
+ }
+
+ private static void unselectWidgetCell(ViewGroup parent, WidgetItem item) {
+ if (parent == null || item == null) return;
+ WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell wc
+ && wc.matchesItem(item));
+ if (cell != null && cell.isShowingAddButton()) {
+ cell.hideAddButton(/* animate= */ false);
+ }
+ }
+
@Override
public void setInsets(Rect insets) {
super.setInsets(insets);
@@ -372,6 +477,23 @@
return true;
}
+ @Override
+ protected int getHeaderTopClip(@NonNull WidgetCell cell) {
+ return 0;
+ }
+
+ @Override
+ protected void scrollCellContainerByY(WidgetCell wc, int scrollByY) {
+ for (ViewParent parent = wc.getParent(); parent != null; parent = parent.getParent()) {
+ if (parent instanceof ScrollView scrollView) {
+ scrollView.smoothScrollBy(0, scrollByY);
+ return;
+ } else if (parent == this) {
+ return;
+ }
+ }
+ }
+
/**
* This is a listener for when the selected header gets changed in the left pane.
*/
diff --git a/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSize.kt b/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSize.kt
new file mode 100644
index 0000000..a0414ba
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSize.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.util
+
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.model.WidgetItem
+import kotlin.math.abs
+
+/** Size of a preview container in terms of the grid spans. */
+data class WidgetPreviewContainerSize(@JvmField val spanX: Int, @JvmField val spanY: Int) {
+ companion object {
+ /**
+ * Returns the size of the preview container in which the given widget's preview should be
+ * displayed (by scaling it if necessary).
+ */
+ fun forItem(item: WidgetItem, dp: DeviceProfile): WidgetPreviewContainerSize {
+ val sizes =
+ if (dp.isTablet && !dp.isTwoPanels) {
+ TABLET_WIDGET_PREVIEW_SIZES
+ } else {
+ HANDHELD_WIDGET_PREVIEW_SIZES
+ }
+
+ for ((index, containerSize) in sizes.withIndex()) {
+ if (containerSize.spanX == item.spanX && containerSize.spanY == item.spanY) {
+ return containerSize // Exact match!
+ }
+ if (containerSize.spanX <= item.spanX && containerSize.spanY <= item.spanY) {
+ return findClosestFittingContainer(
+ containerSizes = sizes.toList(),
+ startIndex = index,
+ item = item
+ )
+ }
+ }
+ // Use largest container if no match found
+ return sizes.elementAt(0)
+ }
+
+ private fun findClosestFittingContainer(
+ containerSizes: List<WidgetPreviewContainerSize>,
+ startIndex: Int,
+ item: WidgetItem
+ ): WidgetPreviewContainerSize {
+ // Checks if it's a smaller container, but close enough to keep the down-scale minimal.
+ fun hasAcceptableSize(currentIndex: Int): Boolean {
+ val container = containerSizes[currentIndex]
+ val isSmallerThanItem =
+ container.spanX <= item.spanX && container.spanY <= item.spanY
+ val isCloseToItemSize =
+ (item.spanY - container.spanY <= 1) && (item.spanX - container.spanX <= 1)
+
+ return isSmallerThanItem && isCloseToItemSize
+ }
+
+ var currentIndex = startIndex
+ var match = containerSizes[currentIndex]
+ val itemCellSizeRatio = item.spanX.toFloat() / item.spanY
+ var lastCellSizeRatioDiff = Float.MAX_VALUE
+
+ // Look for a smaller container (up to an acceptable extent) with closest cell size
+ // ratio.
+ while (currentIndex <= containerSizes.lastIndex && hasAcceptableSize(currentIndex)) {
+ val current = containerSizes[currentIndex]
+ val currentCellSizeRatio = current.spanX.toFloat() / current.spanY
+ val currentCellSizeRatioDiff = abs(itemCellSizeRatio - currentCellSizeRatio)
+
+ if (currentCellSizeRatioDiff < lastCellSizeRatioDiff) {
+ lastCellSizeRatioDiff = currentCellSizeRatioDiff
+ match = containerSizes[currentIndex]
+ }
+ currentIndex++
+ }
+ return match
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizes.kt b/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizes.kt
new file mode 100644
index 0000000..a016676
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizes.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.util
+
+/**
+ * An ordered list of recommended sizes for the preview containers in handheld devices.
+ *
+ * Size of the preview container in which a widget's preview can be displayed.
+ */
+val HANDHELD_WIDGET_PREVIEW_SIZES: List<WidgetPreviewContainerSize> =
+ listOf(
+ WidgetPreviewContainerSize(spanX = 4, spanY = 3),
+ WidgetPreviewContainerSize(spanX = 4, spanY = 2),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 3),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 2),
+ WidgetPreviewContainerSize(spanX = 4, spanY = 1),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 1),
+ WidgetPreviewContainerSize(spanX = 1, spanY = 1),
+ )
+
+/**
+ * An ordered list of recommended sizes for the preview containers in tablet devices (with larger
+ * grids).
+ *
+ * Size of the preview container in which a widget's preview can be displayed (by scaling the
+ * preview if necessary).
+ */
+val TABLET_WIDGET_PREVIEW_SIZES: List<WidgetPreviewContainerSize> =
+ listOf(
+ WidgetPreviewContainerSize(spanX = 3, spanY = 4),
+ WidgetPreviewContainerSize(spanX = 3, spanY = 3),
+ WidgetPreviewContainerSize(spanX = 3, spanY = 2),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 3),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 2),
+ WidgetPreviewContainerSize(spanX = 3, spanY = 1),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 1),
+ WidgetPreviewContainerSize(spanX = 1, spanY = 1),
+ )
diff --git a/src/com/android/launcher3/widget/util/WidgetDragScaleUtils.java b/src/com/android/launcher3/widget/util/WidgetDragScaleUtils.java
new file mode 100644
index 0000000..b8e7248
--- /dev/null
+++ b/src/com/android/launcher3/widget/util/WidgetDragScaleUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.util;
+
+import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
+
+import android.content.Context;
+import android.util.Size;
+
+import androidx.annotation.Px;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
+
+/** Utility classes to evaluate widget scale during drag and drops. **/
+public final class WidgetDragScaleUtils {
+ // Widgets are 5% scaled down relative to their size to have shadow display well inside the
+ // drop target frame (if its possible to scale it down within visible area under the finger).
+ private static final float WIDGET_SCALE_DOWN = 0.05f;
+
+ /**
+ * Returns the scale to be applied to given dragged view to scale it down relative to the
+ * spring loaded workspace. Applies additional scale down offset to get it a little inside
+ * the drop target frame. If the relative scale is smaller than minimum size needed to keep the
+ * view visible under the finger, scale down is performed only until the minimum size.
+ */
+ @Px
+ public static float getWidgetDragScalePx(Context context, DeviceProfile deviceProfile,
+ @Px float draggedViewWidthPx, @Px float draggedViewHeightPx, ItemInfo itemInfo) {
+ int minSize = context.getResources().getDimensionPixelSize(
+ R.dimen.widget_drag_view_min_scale_down_size);
+ Size widgetSizesPx = getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY);
+
+ // We add workspace spring load scale, since the widget's drop target is also scaled, so
+ // the widget size is essentially that smaller.
+ float desiredWidgetScale = deviceProfile.getWorkspaceSpringLoadScale(context)
+ - WIDGET_SCALE_DOWN;
+ float desiredWidgetWidthPx = Math.max(minSize,
+ (desiredWidgetScale * widgetSizesPx.getWidth()));
+ float desiredWidgetHeightPx = Math.max(minSize,
+ desiredWidgetScale * widgetSizesPx.getHeight());
+
+ final float bitmapAspectRatio = draggedViewWidthPx / draggedViewHeightPx;
+ final float containerAspectRatio = desiredWidgetWidthPx / desiredWidgetHeightPx;
+
+ // This downscales large views to fit inside drop target frame. Smaller drawable views may
+ // be up-scaled if they are smaller than the min size;
+ final float scale = bitmapAspectRatio >= containerAspectRatio ? desiredWidgetWidthPx
+ / draggedViewWidthPx : desiredWidgetHeightPx / draggedViewHeightPx;
+ // scale in terms of dp to be applied to the drag shadow during drag and drop
+ return (draggedViewWidthPx * scale) - draggedViewWidthPx;
+ }
+}
diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
index 74d3062..edaf474 100644
--- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
+++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
@@ -16,11 +16,13 @@
package com.android.launcher3.widget.util;
import android.content.Context;
+import android.util.Size;
import androidx.annotation.Px;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
import java.util.ArrayList;
import java.util.Comparator;
@@ -29,12 +31,13 @@
/** An utility class which groups {@link WidgetItem}s into a table. */
public final class WidgetsTableUtils {
+ private static final int MAX_ITEMS_IN_ROW = 3;
/**
* Groups widgets in the following order:
* 1. Widgets always go before shortcuts.
- * 2. Widgets with smaller horizontal spans will be shown first.
- * 3. If widgets have the same horizontal spans, then widgets with a smaller vertical spans will
+ * 2. Widgets with smaller vertical spans will be shown first.
+ * 3. If widgets have the same vertical spans, then widgets with a smaller horizontal spans will
* go first.
* 4. If both widgets have the same horizontal and vertical spans, they will use the same order
* from the given {@code widgetItems}.
@@ -43,14 +46,29 @@
if (item.widgetInfo != null && otherItem.widgetInfo == null) return -1;
if (item.widgetInfo == null && otherItem.widgetInfo != null) return 1;
- if (item.spanX == otherItem.spanX) {
- if (item.spanY == otherItem.spanY) return 0;
- return item.spanY > otherItem.spanY ? 1 : -1;
+ if (item.spanY == otherItem.spanY) {
+ if (item.spanX == otherItem.spanX) return 0;
+ return item.spanX > otherItem.spanX ? 1 : -1;
}
- return item.spanX > otherItem.spanX ? 1 : -1;
+ return item.spanY > otherItem.spanY ? 1 : -1;
};
/**
+ * Comparator that enables displaying rows in increasing order of their size (totalW * H);
+ * except for shortcuts which always show at the bottom.
+ */
+ public static final Comparator<ArrayList<WidgetItem>> WIDGETS_TABLE_ROW_SIZE_COMPARATOR =
+ Comparator.comparingInt(row -> {
+ if (row.stream().anyMatch(WidgetItem::isShortcut)) {
+ return Integer.MAX_VALUE;
+ } else {
+ int rowWidth = row.stream().mapToInt(w -> w.spanX).sum();
+ int rowHeight = row.get(0).spanY;
+ return (rowWidth * rowHeight);
+ }
+ });
+
+ /**
* Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI
* table. This takes liberty to rearrange widgets to make the table visually appealing.
*/
@@ -59,72 +77,71 @@
final @Px int rowPx, final @Px int cellPadding) {
List<WidgetItem> sortedWidgetItems = widgetItems.stream().sorted(WIDGET_SHORTCUT_COMPARATOR)
.collect(Collectors.toList());
- return groupWidgetItemsUsingRowPxWithoutReordering(sortedWidgetItems, context, dp, rowPx,
+ List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
+ sortedWidgetItems, context, dp, rowPx,
cellPadding);
+ return rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
}
/**
* Groups {@code widgetItems} into a 2D array which matches their appearance in a UI table while
* maintaining their order. This function is a variant of
- * {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget pixels for
- * calculation.
+ * {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget container's
+ * pixels for calculation.
*
* <p>Grouping:
* 1. Widgets and shortcuts never group together in the same row.
- * 2. The ordered widgets are grouped together in the same row until their individual occupying
- * pixels exceed the total allowed pixels for the cell.
+ * 2. Widgets are grouped together only if they have same preview container size.
+ * 3. Widgets are grouped together in the same row until the total of individual container sizes
+ * exceed the total allowed pixels for the row.
* 3. The ordered shortcuts are grouped together in the same row until their individual
* occupying pixels exceed the total allowed pixels for the cell.
* 4. If there is only one widget in a row, its width may exceed the {@code rowPx}.
*
- * <p>Let's say the {@code rowPx} is set to 600 and we have 5 widgets. Widgets can be grouped
- * in the same row if each of their individual occupying pixels does not exceed
- * {@code rowPx} / 5 - 2 * {@code cellPadding}.
- * Example 1: Row 1: 200x200, 200x300, 100x100. Average horizontal pixels is 200 and no widgets
- * exceed that width. This is okay.
- * Example 2: Row 1: 200x200, 400x300, 100x100. Average horizontal pixels is 200 and one widget
- * exceed that width. This is not allowed.
- * Example 3: Row 1: 700x400. This is okay because this is the only item in the row.
+ * <p>See WidgetTableUtilsTest
*/
public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithoutReordering(
List<WidgetItem> widgetItems, Context context, final DeviceProfile dp,
final @Px int rowPx, final @Px int cellPadding) {
-
List<ArrayList<WidgetItem>> widgetItemsTable = new ArrayList<>();
ArrayList<WidgetItem> widgetItemsAtRow = null;
+ // A row displays only items of same container size.
+ WidgetPreviewContainerSize containerSizeForRow = null;
+ @Px int currentRowWidth = 0;
+
for (WidgetItem widgetItem : widgetItems) {
if (widgetItemsAtRow == null) {
widgetItemsAtRow = new ArrayList<>();
widgetItemsTable.add(widgetItemsAtRow);
}
int numOfWidgetItems = widgetItemsAtRow.size();
- @Px int individualSpan = (rowPx / (numOfWidgetItems + 1)) - (2 * cellPadding);
+
+ WidgetPreviewContainerSize containerSize =
+ WidgetPreviewContainerSize.Companion.forItem(widgetItem, dp);
+ Size containerSizePx = WidgetSizes.getWidgetSizePx(dp, containerSize.spanX,
+ containerSize.spanY);
+ @Px int containerWidth = containerSizePx.getWidth() + (2 * cellPadding);
+
if (numOfWidgetItems == 0) {
widgetItemsAtRow.add(widgetItem);
- } else if (
- // Since the size of the widget cell is determined by dividing the maximum span
- // pixels evenly, making sure that each widget would have enough span pixels to
- // show their contents.
- widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))
- && widgetItemsAtRow.stream().allMatch(
- item -> WidgetSizes.getWidgetItemSizePx(context, dp, item)
- .getWidth() <= individualSpan)
- && WidgetSizes.getWidgetItemSizePx(context, dp, widgetItem)
- .getWidth() <= individualSpan) {
+ containerSizeForRow = containerSize;
+ currentRowWidth = containerWidth;
+ } else if (widgetItemsAtRow.size() < MAX_ITEMS_IN_ROW
+ && (currentRowWidth + containerWidth) <= rowPx
+ && widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))
+ && containerSize.equals(containerSizeForRow)) {
// Group items in the same row if
// 1. they are with the same type, i.e. a row can only have widgets or shortcuts but
// never a mix of both.
- // 2. Each widget will have horizontal cell span pixels that is at least as large as
- // it is required to fit in the horizontal content, unless the widget horizontal
- // span pixels is larger than the maximum allowed.
- // If an item has horizontal span pixels larger than the maximum allowed pixels
- // per row, we just place it in its own row regardless of the horizontal span
- // limit.
+ // 2. Each widget in the given row has same preview container size.
widgetItemsAtRow.add(widgetItem);
+ currentRowWidth += containerWidth;
} else {
widgetItemsAtRow = new ArrayList<>();
widgetItemsTable.add(widgetItemsAtRow);
widgetItemsAtRow.add(widgetItem);
+ containerSizeForRow = containerSize;
+ currentRowWidth = containerWidth;
}
}
return widgetItemsTable;
diff --git a/src_build_config/com/android/launcher3/BuildConfig.java b/src_build_config/com/android/launcher3/BuildConfig.java
index 3841969..6d4f56d 100644
--- a/src_build_config/com/android/launcher3/BuildConfig.java
+++ b/src_build_config/com/android/launcher3/BuildConfig.java
@@ -35,4 +35,9 @@
* Flag to control various developer centric features
*/
public static final boolean IS_DEBUG_DEVICE = false;
+
+ // Flag to control widgets support in Launcher
+ public static final boolean WIDGETS_ENABLED = true;
+ // Flag to control notification dots support in Launcher
+ public static final boolean NOTIFICATION_DOTS_ENABLED = true;
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
similarity index 97%
rename from src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
rename to src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
index b62dbd1..9865516 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -53,6 +53,11 @@
}
@Override
+ public int getTitle() {
+ return R.string.all_apps_label;
+ }
+
+ @Override
public int getVisibleElements(Launcher launcher) {
return ALL_APPS_CONTENT;
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
similarity index 100%
rename from src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
rename to src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
diff --git a/src_plugins/com/android/systemui/plugins/NetworkFetcherPlugin.java b/src_plugins/com/android/systemui/plugins/NetworkFetcherPlugin.java
new file mode 100644
index 0000000..e327648
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/NetworkFetcherPlugin.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin to proxy network requests
+ */
+@ProvidesInterface(action = NetworkFetcherPlugin.ACTION, version = NetworkFetcherPlugin.VERSION)
+public interface NetworkFetcherPlugin extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_NETWORK_FETCHER_ACTIONS";
+ int VERSION = 1;
+
+ /** Fetches the provided user and return all byte contents */
+ byte[] fetchUrl(String url) throws Exception;
+}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java b/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
deleted file mode 100644
index 7e73ab5..0000000
--- a/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2017 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 static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Binds the results of {@link com.android.launcher3.model.LoaderTask} to the Callbacks objects.
- */
-public class LauncherBinder extends BaseLauncherBinder {
-
- public LauncherBinder(LauncherAppState app, BgDataModel dataModel,
- AllAppsList allAppsList, Callbacks[] callbacks) {
- super(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
- }
-
- @Override
- public void bindDeepShortcuts() {
- final HashMap<ComponentKey, Integer> shortcutMapCopy;
- synchronized (mBgDataModel) {
- shortcutMapCopy = new HashMap<>(mBgDataModel.deepShortcutMap);
- }
- executeCallbacksTask(c -> c.bindDeepShortcutMap(shortcutMapCopy), mUiExecutor);
- }
-
- @Override
- public void bindWidgets() {
- final List<WidgetsListBaseEntry> widgets =
- mBgDataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
- executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
- }
-
- @Override
- public void bindSmartspaceWidget() {
- executeCallbacksTask(c -> c.bindSmartspaceWidget(), mUiExecutor);
- }
-}
diff --git a/src_shortcuts_overrides/com/android/launcher3/util/AbsGridOccupancy.java b/src_shortcuts_overrides/com/android/launcher3/util/AbsGridOccupancy.java
deleted file mode 100644
index 968b281..0000000
--- a/src_shortcuts_overrides/com/android/launcher3/util/AbsGridOccupancy.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-/**
- * Defines method to find the next vacant cell on a grid.
- * This uses the default top-down, left-right approach and can be over-written through
- * code swaps in different launchers.
- */
-public abstract class AbsGridOccupancy {
- /**
- * Find the first vacant cell, if there is one.
- *
- * @param vacantOut Holds the x and y coordinate of the vacant cell
- * @param spanX Horizontal cell span.
- * @param spanY Vertical cell span.
- *
- * @return true if a vacant cell was found
- */
- protected boolean findVacantCell(int[] vacantOut, boolean[][] cells, int countX, int countY,
- int spanX, int spanY) {
- for (int y = 0; (y + spanY) <= countY; y++) {
- for (int x = 0; (x + spanX) <= countX; x++) {
- boolean available = !cells[x][y];
- out:
- for (int i = x; i < x + spanX; i++) {
- for (int j = y; j < y + spanY; j++) {
- available = available && !cells[i][j];
- if (!available) break out;
- }
- }
- if (available) {
- vacantOut[0] = x;
- vacantOut[1] = y;
- return true;
- }
- }
- }
- return false;
- }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
deleted file mode 100644
index efde7d8..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import android.app.ActivityOptions;
-import android.app.Person;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.ArrayMap;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.UserIconInfo;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A wrapper for the hidden API calls
- */
-public class ApiWrapper {
-
- public static final boolean TASKBAR_DRAWN_IN_PROCESS = false;
-
- public static Person[] getPersons(ShortcutInfo si) {
- return Utilities.EMPTY_PERSON_ARRAY;
- }
-
- public static Map<String, LauncherActivityInfo> getActivityOverrides(Context context) {
- return Collections.emptyMap();
- }
-
- /**
- * Creates an ActivityOptions to play fade-out animation on closing targets
- */
- public static ActivityOptions createFadeOutAnimOptions(Context context) {
- return ActivityOptions.makeCustomAnimation(context, 0, android.R.anim.fade_out);
- }
-
- /**
- * Returns a map of all users on the device to their corresponding UI properties
- */
- public static Map<UserHandle, UserIconInfo> queryAllUsers(Context context) {
- UserManager um = context.getSystemService(UserManager.class);
- Map<UserHandle, UserIconInfo> users = new ArrayMap<>();
- List<UserHandle> usersActual = um.getUserProfiles();
- if (usersActual != null) {
- for (UserHandle user : usersActual) {
- long serial = um.getSerialNumberForUser(user);
-
- // Simple check to check if the provided user is work profile
- // TODO: Migrate to a better platform API
- NoopDrawable d = new NoopDrawable();
- boolean isWork = (d != context.getPackageManager().getUserBadgedIcon(d, user));
- UserIconInfo info = new UserIconInfo(
- user,
- isWork ? UserIconInfo.TYPE_WORK : UserIconInfo.TYPE_MAIN,
- serial);
- users.put(user, info);
- }
- }
- return users;
- }
-
- /**
- * Returns the list of the system packages that are installed at user creation.
- * An empty list denotes that all system packages are installed for that user at creation.
- */
- public static List<String> getPreInstalledSystemPackages(Context context, UserHandle user) {
- return new ArrayList<>();
- }
-
- /**
- * Returns an intent which can be used to start the App Market activity (Installer
- * Activity).
- */
- public static Intent getAppMarketActivityIntent(Context context, String packageName,
- UserHandle user) {
- return new Intent(Intent.ACTION_VIEW)
- .setData(new Uri.Builder()
- .scheme("market")
- .authority("details")
- .appendQueryParameter("id", packageName)
- .build())
- .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
- .authority(context.getPackageName()).build());
- }
-
- /**
- * Checks if an activity is flagged as non-resizeable.
- */
- public static boolean isNonResizeableActivity(LauncherActivityInfo lai) {
- // Overridden in quickstep
- return false;
- }
-
-
- private static class NoopDrawable extends ColorDrawable {
- @Override
- public int getIntrinsicHeight() {
- return 1;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return 1;
- }
- }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PredictedAppIconInflater.java b/src_ui_overrides/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
deleted file mode 100644
index 4893c17..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-
-/** A util class that inflates a predicted app icon */
-public class PredictedAppIconInflater {
- public static View inflate(LayoutInflater inflater, ViewGroup parent, WorkspaceItemInfo info) {
- return null;
- }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java b/src_ui_overrides/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
deleted file mode 100644
index 6afa446..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides.flags;
-
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceFragmentCompat;
-
-/**
- * Place holder class for developer options.
- */
-public class DeveloperOptionsUI {
-
- public DeveloperOptionsUI(PreferenceFragmentCompat fragment, PreferenceCategory flags) { }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
deleted file mode 100644
index b193d37..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides.flags;
-
-import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.ConstantItem;
-import com.android.launcher3.config.FeatureFlags.BooleanFlag;
-import com.android.launcher3.config.FeatureFlags.FlagState;
-import com.android.launcher3.config.FeatureFlags.IntFlag;
-
-import java.io.PrintWriter;
-
-/**
- * Helper class to create various flags for launcher build. The base implementation does
- * not provide any flagging system, and simply replies with the default value.
- */
-public class FlagsFactory {
-
- /**
- * Creates a new debug flag
- */
- public static BooleanFlag getDebugFlag(
- int bugId, String key, FlagState flagState, String description) {
- return new BooleanFlag(flagState == ENABLED);
- }
-
- /**
- * Creates a new debug flag
- */
- public static BooleanFlag getReleaseFlag(
- int bugId, String key, FlagState flagState, String description) {
- return new BooleanFlag(flagState == ENABLED);
- }
-
- /**
- * Creates a new integer flag. Integer flags are always release flags
- */
- public static IntFlag getIntFlag(
- int bugId, String key, int defaultValueInCode, String description) {
- return new IntFlag(defaultValueInCode);
- }
-
- /**
- * Creates a new debug integer flag and it is saved in LauncherPrefs.
- */
- public static IntFlag getIntFlag(
- int bugId, String key, int defaultValueInCode, String description,
- @Nullable ConstantItem<Integer> launcherPrefFlag) {
- return new IntFlag(defaultValueInCode);
- }
-
- /**
- * Dumps the current flags state to the print writer
- */
- public static void dump(PrintWriter pw) { }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
deleted file mode 100644
index e1a35c9..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ /dev/null
@@ -1,64 +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.uioverrides.plugins;
-
-import android.content.ComponentName;
-import android.content.Context;
-
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginListener;
-
-import java.util.Collections;
-import java.util.Set;
-
-import androidx.preference.PreferenceDataStore;
-
-public class PluginManagerWrapper {
-
- public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
- new MainThreadInitializedObject<>(PluginManagerWrapper::new);
-
- private static final String PREFIX_PLUGIN_ENABLED = "PLUGIN_ENABLED_";
- public static final String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED";
-
- private PluginManagerWrapper(Context c) {
- }
-
- public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
- }
-
- public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
- boolean allowMultiple) {
- }
-
- public void removePluginListener(PluginListener<? extends Plugin> listener) { }
-
- public Set<String> getPluginActions() {
- return Collections.emptySet();
- }
-
- public PreferenceDataStore getPluginEnabler() {
- return new PreferenceDataStore() { };
- }
-
- public static String pluginEnabledKey(ComponentName cn) {
- return PREFIX_PLUGIN_ENABLED + cn.flattenToString();
- }
-
- public static boolean hasPlugins(Context context) {
- return false;
- }
-}
diff --git a/tests/Android.bp b/tests/Android.bp
index 8ce9507..d6a4fdc 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -31,29 +31,15 @@
],
}
-// Source code used for screenshot tests
filegroup {
- name: "launcher-image-tests-helpers",
+ name: "launcher3-robo-src",
+ // multivalentTests directory is a shared folder for not only robolectric converted test
+ // classes but also shared helper classes.
srcs: [
- "src/com/android/launcher3/celllayout/board/*.java",
- "src/com/android/launcher3/celllayout/board/*.kt",
- "src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java",
- "src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
- "src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
- "src/com/android/launcher3/ui/TestViewHelpers.java",
- "multivalentTests/src/com/android/launcher3/util/LauncherLayoutBuilder.java",
- "src/com/android/launcher3/util/ModelTestExtensions.kt",
- "src/com/android/launcher3/util/TestConstants.java",
- "multivalentTests/src/com/android/launcher3/util/TestUtil.java",
- "src/com/android/launcher3/util/Wait.java",
- "multivalentTests/src/com/android/launcher3/util/WidgetUtils.java",
- "src/com/android/launcher3/util/rule/*.java",
- "src/com/android/launcher3/util/rule/*.kt",
- "multivalentTests/src/com/android/launcher3/util/rule/*.java",
- "multivalentTests/src/com/android/launcher3/util/rule/*.kt",
- "src/com/android/launcher3/util/viewcapture_analysis/*.java",
- "src/com/android/launcher3/testcomponent/*.java",
- "src/com/android/launcher3/testcomponent/*.kt",
+ "multivalentTests/src/**/*.java",
+ "multivalentTests/src/**/*.kt",
+ "src_deviceless/**/*.java",
+ "src_deviceless/**/*.kt",
],
}
@@ -70,35 +56,15 @@
filegroup {
name: "launcher-oop-tests-src",
srcs: [
+ ":launcher-testing-helpers",
"src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java",
"src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java",
"src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java",
"src/com/android/launcher3/dragging/TaplDragTest.java",
"src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java",
- "src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
- "src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
"src/com/android/launcher3/ui/TaplTestsLauncher3Test.java",
"src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java",
"src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java",
- "multivalentTests/src/com/android/launcher3/util/LauncherLayoutBuilder.java",
- "src/com/android/launcher3/util/TestConstants.java",
- "multivalentTests/src/com/android/launcher3/util/TestUtil.java",
- "src/com/android/launcher3/util/Wait.java",
- "multivalentTests/src/com/android/launcher3/util/WidgetUtils.java",
- "src/com/android/launcher3/util/rule/FailureWatcher.java",
- "src/com/android/launcher3/util/rule/ViewCaptureRule.kt",
- "src/com/android/launcher3/util/rule/SamplerRule.java",
- "src/com/android/launcher3/util/rule/ScreenRecordRule.java",
- "src/com/android/launcher3/util/rule/ShellCommandRule.java",
- "src/com/android/launcher3/util/rule/TestIsolationRule.java",
- "multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java",
- "src/com/android/launcher3/util/viewcapture_analysis/*.java",
- "src/com/android/launcher3/testcomponent/BaseTestingActivity.java",
- "src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java",
- "src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java",
- "src/com/android/launcher3/testcomponent/TestCommandReceiver.java",
- "src/com/android/launcher3/testcomponent/TestLauncherActivity.java",
- "src/com/android/launcher3/testcomponent/ImeTestActivity.java",
],
}
@@ -123,6 +89,7 @@
"mockito-target-extended-minus-junit4",
"launcher_log_protos_lite",
"truth",
+ "kotlinx_coroutines_test",
"platform-test-rules",
"testables",
"com_android_launcher3_flags_lib",
@@ -138,6 +105,7 @@
android_library {
name: "Launcher3TestResources",
resource_dirs: ["res"],
+ asset_dirs: ["assets"],
// TODO(b/319712088): re-enable use_resource_processor
use_resource_processor: false,
}
@@ -177,7 +145,7 @@
name: "launcher-testing-shared",
srcs: [
"multivalentTests/shared/com/android/launcher3/testing/shared/**/*.java",
- "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt"
+ "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt",
],
resource_dirs: [],
manifest: "multivalentTests/shared/AndroidManifest.xml",
@@ -205,28 +173,11 @@
],
}
-android_library {
- name: "Launcher3Lib",
- srcs: [
- ":launcher-src",
- ":launcher-src_shortcuts_overrides",
- ":launcher-src_ui_overrides",
- ],
- static_libs: [
- "Launcher3CommonDepsLib",
- ],
- // TODO(b/319712088): re-enable use_resource_processor
- use_resource_processor: false,
-}
-
android_robolectric_test {
enabled: true,
name: "Launcher3RoboTests",
- // multivalentTests directory is a shared folder for not only robolectric converted test
- // classes but also shared helper classes.
srcs: [
- "multivalentTests/src/com/android/launcher3/util/*.java",
- "multivalentTests/src/com/android/launcher3/util/*.kt",
+ ":launcher3-robo-src",
// Test util classes
":launcher-testing-helpers",
@@ -253,11 +204,14 @@
"androidx.test.rules",
"uiautomator-helpers",
"inline-mockito-robolectric-prebuilt",
+ "mockito-kotlin-nodeps",
"platform-parametric-runner-lib",
+ "platform-test-rules-deviceless",
"testables",
"Launcher3TestResources",
"SystemUISharedLib",
"launcher-testing-shared",
+ "android.appwidget.flags-aconfig-java",
],
libs: [
"android.test.runner.stubs.system",
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 7cb7964..4b926a8 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -319,6 +319,15 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
+ <activity-alias android:name="ActivityNoLabel"
+ android:label=""
+ android:exported="true"
+ android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity-alias>
<activity-alias android:name="MaxShortcutsActivity"
android:label="TestActivityMaxShortcuts"
android:exported="true"
@@ -388,11 +397,24 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
+ <activity android:name="com.android.launcher3.testcomponent.ExcludeFromRecentsTestActivity"
+ android:label="ExcludeFromRecentsTestActivity"
+ android:exported="true"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
- <!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
+ <!-- Disable eager initialization of Jetpack libraries. See bug 197780098. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
+
+ <property
+ android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
+ android:value="true"/>
</application>
</manifest>
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
index bcbe343..270a610 100644
--- a/tests/Launcher3Tests.xml
+++ b/tests/Launcher3Tests.xml
@@ -13,14 +13,27 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- This test config file is auto-generated. -->
+
<configuration description="Runs Launcher3 tests.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
+ <option name="max-tmp-logcat-file" value="104857600" /> <!-- 100 * 1024 * 1024 -->
+
+ <logger class="com.android.tradefed.log.FileLogger">
+ <option name="max-log-size" value="20" />
+ </logger>
+
+ <!-- Disables the "Ramdump uploader to betterbug" -->
+ <option name="post-boot-command" value="am broadcast --async --user 0 -a com.google.gservices.intent.action.GSERVICES_OVERRIDE -e betterbug_enable_ramdump_uploader false" />
+
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<option name="set-test-harness" value="true" />
- <option name="run-command" value="am force-stop com.android.launcher3" />
+
+ <option name="run-command" value="svc nfc disable" />
+ <option name="run-command" value="settings put global ble_scan_always_enabled 0" />
+ <option name="run-command" value="svc bluetooth disable" />
+
<option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher" />
<option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher.out_of_proc_tests" />
<option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher.tests" />
@@ -30,6 +43,11 @@
<option name="run-command" value="settings delete secure assistant" />
<option name="run-command" value="settings put global airplane_mode_on 1" />
<option name="run-command" value="am broadcast -a android.intent.action.AIRPLANE_MODE" />
+
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
+
+ <option name="run-command" value="settings put system pointer_location 1" />
+ <option name="run-command" value="settings put system show_touches 1" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/assets/databases/BackupAndRestore/launcher.db b/tests/assets/databases/BackupAndRestore/launcher.db
new file mode 100644
index 0000000..126d166
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher.db
Binary files differ
diff --git a/tests/assets/databases/BackupAndRestore/launcher_3_by_3.db b/tests/assets/databases/BackupAndRestore/launcher_3_by_3.db
new file mode 100644
index 0000000..6d8cd73
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher_3_by_3.db
Binary files differ
diff --git a/tests/assets/databases/BackupAndRestore/launcher_4_by_4.db b/tests/assets/databases/BackupAndRestore/launcher_4_by_4.db
new file mode 100644
index 0000000..00061dd
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher_4_by_4.db
Binary files differ
diff --git a/tests/assets/databases/BackupAndRestore/launcher_4_by_5.db b/tests/assets/databases/BackupAndRestore/launcher_4_by_5.db
new file mode 100644
index 0000000..e2e65aa
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher_4_by_5.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db b/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db
new file mode 100644
index 0000000..8bea3ce
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to3x3.db b/tests/assets/databases/GridMigrationTest/result5x5to3x3.db
new file mode 100644
index 0000000..686056d
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/result5x5to3x3.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to4x7.db b/tests/assets/databases/GridMigrationTest/result5x5to4x7.db
new file mode 100644
index 0000000..cd105c5
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/result5x5to4x7.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to5x8.db b/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
new file mode 100644
index 0000000..4b46969
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/test_launcher.db b/tests/assets/databases/GridMigrationTest/test_launcher.db
new file mode 100644
index 0000000..c680e95
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/test_launcher.db
Binary files differ
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
index af8f67f..82a6310 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
hotseatBarSizePx: 273.0px (104.0dp)
- inv.hotseatColumnSpan: 5
+ mHotseatColumnSpan: 5
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 166.0px (63.238094dp)
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 42.0px (16.0dp)
getCellLayoutSpringLoadShrunkTop(): 391.0px (148.95238dp)
getCellLayoutSpringLoadShrunkBottom(): 1927.0px (734.0952dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
- getWorkspaceSpringLoadScale(): 0.7781155px (0.29642496dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+ getWorkspaceSpringLoadScale(): 0.7666667px (0.2920635dp)
getCellLayoutHeight(): 1974.0px (752.0dp)
getCellLayoutWidth(): 1080.0px (411.42856dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
index 5b83dd7..4271105 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
hotseatBarSizePx: 294.0px (112.0dp)
- inv.hotseatColumnSpan: 5
+ mHotseatColumnSpan: 5
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 166.0px (63.238094dp)
hotseatBarBottomSpacePx: 147.0px (56.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 42.0px (16.0dp)
getCellLayoutSpringLoadShrunkTop(): 391.0px (148.95238dp)
getCellLayoutSpringLoadShrunkBottom(): 1906.0px (726.0952dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
- getWorkspaceSpringLoadScale(): 0.77572966px (0.29551607dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+ getWorkspaceSpringLoadScale(): 0.76666665px (0.29206347dp)
getCellLayoutHeight(): 1953.0px (744.0dp)
getCellLayoutWidth(): 1080.0px (411.42856dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
index 03a0048..8bd6b99 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
hotseatBarSizePx: 252.0px (96.0dp)
- inv.hotseatColumnSpan: 5
+ mHotseatColumnSpan: 5
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 166.0px (63.238094dp)
hotseatBarBottomSpacePx: 0.0px (0.0dp)
mHotseatBarEdgePaddingPx: 63.0px (24.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 16.0px (6.095238dp)
getCellLayoutSpringLoadShrunkTop(): 201.0px (76.57143dp)
getCellLayoutSpringLoadShrunkBottom(): 961.0px (366.09525dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8059385px (0.30702418dp)
getCellLayoutHeight(): 943.0px (359.2381dp)
getCellLayoutWidth(): 2073.0px (789.7143dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
index 84258b3..8dbb413 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
hotseatBarSizePx: 252.0px (96.0dp)
- inv.hotseatColumnSpan: 5
+ mHotseatColumnSpan: 5
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 166.0px (63.238094dp)
hotseatBarBottomSpacePx: 0.0px (0.0dp)
mHotseatBarEdgePaddingPx: 63.0px (24.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 16.0px (6.095238dp)
getCellLayoutSpringLoadShrunkTop(): 201.0px (76.57143dp)
getCellLayoutSpringLoadShrunkBottom(): 1017.0px (387.42856dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8111332px (0.3090031dp)
getCellLayoutHeight(): 1006.0px (383.2381dp)
getCellLayoutWidth(): 1947.0px (741.7143dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
index 87a9700..ab4b286 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 32.0px (16.0dp)
allAppsLeftRightMargin: 412.0px (206.0dp)
hotseatBarSizePx: 200.0px (100.0dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 6
+ mHotseatWidthPx: 1960.0px (980.0dp)
hotseatCellHeightPx: 135.0px (67.5dp)
hotseatBarBottomSpacePx: 80.0px (40.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -82,12 +83,12 @@
springLoadedHotseatBarTopMarginPx: 128.0px (64.0dp)
getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
getHotseatLayoutPadding(context).bottom: 65.0px (32.5dp)
- getHotseatLayoutPadding(context).left: 668.0px (334.0dp)
- getHotseatLayoutPadding(context).right: 668.0px (334.0dp)
+ getHotseatLayoutPadding(context).left: 300.0px (150.0dp)
+ getHotseatLayoutPadding(context).right: 300.0px (150.0dp)
numShownHotseatIcons: 6
- hotseatBorderSpace: 100.0px (50.0dp)
+ hotseatBorderSpace: 248.0px (124.0dp)
isQsbInline: false
- hotseatQsbWidth: 1214.0px (607.0dp)
+ hotseatQsbWidth: 1950.0px (975.0dp)
isTaskbarPresent:false
isTaskbarPresentInApps:true
taskbarHeight: 0.0px (0.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 64.0px (32.0dp)
getCellLayoutSpringLoadShrunkTop(): 312.0px (156.0dp)
getCellLayoutSpringLoadShrunkBottom(): 1272.0px (636.0dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 48.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 96.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.76677316px (0.38338658dp)
getCellLayoutHeight(): 1252.0px (626.0dp)
getCellLayoutWidth(): 2198.0px (1099.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
index 169256d..80835bc 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 32.0px (16.0dp)
allAppsLeftRightMargin: 412.0px (206.0dp)
hotseatBarSizePx: 200.0px (100.0dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 6
+ mHotseatWidthPx: 1960.0px (980.0dp)
hotseatCellHeightPx: 135.0px (67.5dp)
hotseatBarBottomSpacePx: 80.0px (40.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -82,12 +83,12 @@
springLoadedHotseatBarTopMarginPx: 128.0px (64.0dp)
getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
getHotseatLayoutPadding(context).bottom: 65.0px (32.5dp)
- getHotseatLayoutPadding(context).left: 668.0px (334.0dp)
- getHotseatLayoutPadding(context).right: 668.0px (334.0dp)
+ getHotseatLayoutPadding(context).left: 300.0px (150.0dp)
+ getHotseatLayoutPadding(context).right: 300.0px (150.0dp)
numShownHotseatIcons: 6
- hotseatBorderSpace: 100.0px (50.0dp)
+ hotseatBorderSpace: 248.0px (124.0dp)
isQsbInline: false
- hotseatQsbWidth: 1214.0px (607.0dp)
+ hotseatQsbWidth: 1950.0px (975.0dp)
isTaskbarPresent:false
isTaskbarPresentInApps:true
taskbarHeight: 0.0px (0.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 64.0px (32.0dp)
getCellLayoutSpringLoadShrunkTop(): 312.0px (156.0dp)
getCellLayoutSpringLoadShrunkBottom(): 1272.0px (636.0dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 48.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 96.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.76677316px (0.38338658dp)
getCellLayoutHeight(): 1252.0px (626.0dp)
getCellLayoutWidth(): 2198.0px (1099.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
index 59da1be..fc53107 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 32.0px (16.0dp)
allAppsLeftRightMargin: 152.0px (76.0dp)
hotseatBarSizePx: 272.0px (136.0dp)
- inv.hotseatColumnSpan: 6
+ mHotseatColumnSpan: 6
+ mHotseatWidthPx: 1300.0px (650.0dp)
hotseatCellHeightPx: 135.0px (67.5dp)
hotseatBarBottomSpacePx: 152.0px (76.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 96.0px (48.0dp)
getCellLayoutSpringLoadShrunkTop(): 564.0px (282.0dp)
getCellLayoutSpringLoadShrunkBottom(): 2072.0px (1036.0dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 48.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 96.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8125px (0.40625dp)
getCellLayoutHeight(): 1856.0px (928.0dp)
getCellLayoutWidth(): 1528.0px (764.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
index ad1f095..836819f 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 32.0px (16.0dp)
allAppsLeftRightMargin: 152.0px (76.0dp)
hotseatBarSizePx: 272.0px (136.0dp)
- inv.hotseatColumnSpan: 6
+ mHotseatColumnSpan: 6
+ mHotseatWidthPx: 1300.0px (650.0dp)
hotseatCellHeightPx: 135.0px (67.5dp)
hotseatBarBottomSpacePx: 152.0px (76.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 96.0px (48.0dp)
getCellLayoutSpringLoadShrunkTop(): 564.0px (282.0dp)
getCellLayoutSpringLoadShrunkBottom(): 2072.0px (1036.0dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 48.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 96.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8125px (0.40625dp)
getCellLayoutHeight(): 1856.0px (928.0dp)
getCellLayoutWidth(): 1528.0px (764.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
index 61d5ee6..108182f 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 42.0px (16.0dp)
allAppsLeftRightMargin: 183.0px (69.71429dp)
hotseatBarSizePx: 267.0px (101.71429dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 159.0px (60.57143dp)
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 42.0px (16.0dp)
getCellLayoutSpringLoadShrunkTop(): 299.0px (113.90476dp)
getCellLayoutSpringLoadShrunkBottom(): 1457.0px (555.0476dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8452555px (0.32200208dp)
getCellLayoutHeight(): 1370.0px (521.9048dp)
getCellLayoutWidth(): 1083.0px (412.57144dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
index ac73e2f..313d2a3 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 42.0px (16.0dp)
allAppsLeftRightMargin: 183.0px (69.71429dp)
hotseatBarSizePx: 267.0px (101.71429dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 159.0px (60.57143dp)
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 42.0px (16.0dp)
getCellLayoutSpringLoadShrunkTop(): 299.0px (113.90476dp)
getCellLayoutSpringLoadShrunkBottom(): 1457.0px (555.0476dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8452555px (0.32200208dp)
getCellLayoutHeight(): 1370.0px (521.9048dp)
getCellLayoutWidth(): 1083.0px (412.57144dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button_decoupleDepth.txt
new file mode 100644
index 0000000..46cce24
--- /dev/null
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button_decoupleDepth.txt
@@ -0,0 +1,130 @@
+DeviceProfile:
+ 1 dp = 2.625 px
+ isTablet:true
+ isPhone:false
+ transposeLayoutWithOrientation:false
+ isGestureMode:false
+ isLandscape:true
+ isMultiWindowMode:false
+ isTwoPanels:true
+ isLeftRightSplit:true
+ windowX: 0.0px (0.0dp)
+ windowY: 0.0px (0.0dp)
+ widthPx: 2208.0px (841.1429dp)
+ heightPx: 1840.0px (700.9524dp)
+ availableWidthPx: 2208.0px (841.1429dp)
+ availableHeightPx: 1730.0px (659.0476dp)
+ mInsets.left: 0.0px (0.0dp)
+ mInsets.top: 110.0px (41.904762dp)
+ mInsets.right: 0.0px (0.0dp)
+ mInsets.bottom: 0.0px (0.0dp)
+ aspectRatio:1.2
+ isResponsiveGrid:false
+ isScalableGrid:false
+ inv.numRows: 4
+ inv.numColumns: 4
+ inv.numSearchContainerColumns: 4
+ minCellSize: PointF(0.0, 0.0)dp
+ cellWidthPx: 154.0px (58.666668dp)
+ cellHeightPx: 218.0px (83.04762dp)
+ getCellSize().x: 270.0px (102.85714dp)
+ getCellSize().y: 342.0px (130.28572dp)
+ cellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)
+ cellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)
+ cellLayoutPaddingPx.left: 0.0px (0.0dp)
+ cellLayoutPaddingPx.top: 0.0px (0.0dp)
+ cellLayoutPaddingPx.right: 0.0px (0.0dp)
+ cellLayoutPaddingPx.bottom: 0.0px (0.0dp)
+ iconSizePx: 141.0px (53.714287dp)
+ iconTextSizePx: 34.0px (12.952381dp)
+ iconDrawablePaddingPx: 13.0px (4.952381dp)
+ numFolderRows: 3
+ numFolderColumns: 4
+ folderCellWidthPx: 189.0px (72.0dp)
+ folderCellHeightPx: 219.0px (83.42857dp)
+ folderChildIconSizePx: 141.0px (53.714287dp)
+ folderChildTextSizePx: 34.0px (12.952381dp)
+ folderChildDrawablePaddingPx: 5.0px (1.9047619dp)
+ folderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)
+ folderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)
+ folderContentPaddingLeftRight: 21.0px (8.0dp)
+ folderTopPadding: 63.0px (24.0dp)
+ folderFooterHeight: 147.0px (56.0dp)
+ bottomSheetTopPadding: 110.0px (41.904762dp)
+ bottomSheetOpenDuration: 500
+ bottomSheetCloseDuration: 500
+ bottomSheetWorkspaceScale: 0.97
+ bottomSheetDepth: 0.3
+ allAppsShiftRange: 1840.0px (700.9524dp)
+ allAppsOpenDuration: 500
+ allAppsCloseDuration: 500
+ allAppsIconSizePx: 141.0px (53.714287dp)
+ allAppsIconTextSizePx: 34.0px (12.952381dp)
+ allAppsIconDrawablePaddingPx: 21.0px (8.0dp)
+ allAppsCellHeightPx: 361.0px (137.5238dp)
+ allAppsCellWidthPx: 183.0px (69.71429dp)
+ allAppsBorderSpacePxX: 42.0px (16.0dp)
+ allAppsBorderSpacePxY: 42.0px (16.0dp)
+ numShownAllAppsColumns: 8
+ allAppsPadding.top: 110.0px (41.904762dp)
+ allAppsPadding.left: 42.0px (16.0dp)
+ allAppsPadding.right: 42.0px (16.0dp)
+ allAppsLeftRightMargin: 183.0px (69.71429dp)
+ hotseatBarSizePx: 267.0px (101.71429dp)
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
+ hotseatCellHeightPx: 159.0px (60.57143dp)
+ hotseatBarBottomSpacePx: 126.0px (48.0dp)
+ mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
+ mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ hotseatBarEndOffset: 0.0px (0.0dp)
+ hotseatQsbSpace: 0.0px (0.0dp)
+ hotseatQsbHeight: 0.0px (0.0dp)
+ springLoadedHotseatBarTopMarginPx: 116.0px (44.190475dp)
+ getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
+ getHotseatLayoutPadding(context).bottom: 108.0px (41.142857dp)
+ getHotseatLayoutPadding(context).left: 113.0px (43.04762dp)
+ getHotseatLayoutPadding(context).right: 113.0px (43.04762dp)
+ numShownHotseatIcons: 6
+ hotseatBorderSpace: 0.0px (0.0dp)
+ isQsbInline: false
+ hotseatQsbWidth: 0.0px (0.0dp)
+ isTaskbarPresent:false
+ isTaskbarPresentInApps:true
+ taskbarHeight: 0.0px (0.0dp)
+ stashedTaskbarHeight: 0.0px (0.0dp)
+ taskbarBottomMargin: 0.0px (0.0dp)
+ taskbarIconSize: 0.0px (0.0dp)
+ desiredWorkspaceHorizontalMarginPx: 21.0px (8.0dp)
+ workspacePadding.left: 21.0px (8.0dp)
+ workspacePadding.top: 30.0px (11.428572dp)
+ workspacePadding.right: 21.0px (8.0dp)
+ workspacePadding.bottom: 330.0px (125.71429dp)
+ iconScale: 1.0px (0.3809524dp)
+ cellScaleToFit : 1.0px (0.3809524dp)
+ extraSpace: 498.0px (189.71428dp)
+ unscaled extraSpace: 498.0px (189.71428dp)
+ maxEmptySpace: 0.0px (0.0dp)
+ workspaceTopPadding: 0.0px (0.0dp)
+ workspaceBottomPadding: 0.0px (0.0dp)
+ overviewTaskMarginPx: 0.0px (0.0dp)
+ overviewTaskIconSizePx: 0.0px (0.0dp)
+ overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
+ overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
+ overviewActionsTopMarginPx: 0.0px (0.0dp)
+ overviewActionsHeight: 0.0px (0.0dp)
+ overviewActionsClaimedSpaceBelow: 0.0px (0.0dp)
+ overviewActionsButtonSpacing: 0.0px (0.0dp)
+ overviewPageSpacing: 0.0px (0.0dp)
+ overviewRowSpacing: 0.0px (0.0dp)
+ overviewGridSideMargin: 0.0px (0.0dp)
+ dropTargetBarTopMarginPx: 0.0px (0.0dp)
+ dropTargetBarSizePx: 147.0px (56.0dp)
+ dropTargetBarBottomMarginPx: 42.0px (16.0dp)
+ getCellLayoutSpringLoadShrunkTop(): 299.0px (113.90476dp)
+ getCellLayoutSpringLoadShrunkBottom(): 1457.0px (555.0476dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+ getWorkspaceSpringLoadScale(): 0.8452555px (0.32200208dp)
+ getCellLayoutHeight(): 1370.0px (521.9048dp)
+ getCellLayoutWidth(): 1083.0px (412.57144dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape_decoupleDepth.txt
new file mode 100644
index 0000000..44b99e9
--- /dev/null
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape_decoupleDepth.txt
@@ -0,0 +1,130 @@
+DeviceProfile:
+ 1 dp = 2.625 px
+ isTablet:true
+ isPhone:false
+ transposeLayoutWithOrientation:false
+ isGestureMode:true
+ isLandscape:true
+ isMultiWindowMode:false
+ isTwoPanels:true
+ isLeftRightSplit:true
+ windowX: 0.0px (0.0dp)
+ windowY: 0.0px (0.0dp)
+ widthPx: 2208.0px (841.1429dp)
+ heightPx: 1840.0px (700.9524dp)
+ availableWidthPx: 2208.0px (841.1429dp)
+ availableHeightPx: 1730.0px (659.0476dp)
+ mInsets.left: 0.0px (0.0dp)
+ mInsets.top: 110.0px (41.904762dp)
+ mInsets.right: 0.0px (0.0dp)
+ mInsets.bottom: 0.0px (0.0dp)
+ aspectRatio:1.2
+ isResponsiveGrid:false
+ isScalableGrid:false
+ inv.numRows: 4
+ inv.numColumns: 4
+ inv.numSearchContainerColumns: 4
+ minCellSize: PointF(0.0, 0.0)dp
+ cellWidthPx: 154.0px (58.666668dp)
+ cellHeightPx: 218.0px (83.04762dp)
+ getCellSize().x: 270.0px (102.85714dp)
+ getCellSize().y: 342.0px (130.28572dp)
+ cellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)
+ cellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)
+ cellLayoutPaddingPx.left: 0.0px (0.0dp)
+ cellLayoutPaddingPx.top: 0.0px (0.0dp)
+ cellLayoutPaddingPx.right: 0.0px (0.0dp)
+ cellLayoutPaddingPx.bottom: 0.0px (0.0dp)
+ iconSizePx: 141.0px (53.714287dp)
+ iconTextSizePx: 34.0px (12.952381dp)
+ iconDrawablePaddingPx: 13.0px (4.952381dp)
+ numFolderRows: 3
+ numFolderColumns: 4
+ folderCellWidthPx: 189.0px (72.0dp)
+ folderCellHeightPx: 219.0px (83.42857dp)
+ folderChildIconSizePx: 141.0px (53.714287dp)
+ folderChildTextSizePx: 34.0px (12.952381dp)
+ folderChildDrawablePaddingPx: 5.0px (1.9047619dp)
+ folderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)
+ folderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)
+ folderContentPaddingLeftRight: 21.0px (8.0dp)
+ folderTopPadding: 63.0px (24.0dp)
+ folderFooterHeight: 147.0px (56.0dp)
+ bottomSheetTopPadding: 110.0px (41.904762dp)
+ bottomSheetOpenDuration: 500
+ bottomSheetCloseDuration: 500
+ bottomSheetWorkspaceScale: 0.97
+ bottomSheetDepth: 0.3
+ allAppsShiftRange: 1840.0px (700.9524dp)
+ allAppsOpenDuration: 500
+ allAppsCloseDuration: 500
+ allAppsIconSizePx: 141.0px (53.714287dp)
+ allAppsIconTextSizePx: 34.0px (12.952381dp)
+ allAppsIconDrawablePaddingPx: 21.0px (8.0dp)
+ allAppsCellHeightPx: 361.0px (137.5238dp)
+ allAppsCellWidthPx: 183.0px (69.71429dp)
+ allAppsBorderSpacePxX: 42.0px (16.0dp)
+ allAppsBorderSpacePxY: 42.0px (16.0dp)
+ numShownAllAppsColumns: 8
+ allAppsPadding.top: 110.0px (41.904762dp)
+ allAppsPadding.left: 42.0px (16.0dp)
+ allAppsPadding.right: 42.0px (16.0dp)
+ allAppsLeftRightMargin: 183.0px (69.71429dp)
+ hotseatBarSizePx: 267.0px (101.71429dp)
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
+ hotseatCellHeightPx: 159.0px (60.57143dp)
+ hotseatBarBottomSpacePx: 126.0px (48.0dp)
+ mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
+ mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ hotseatBarEndOffset: 0.0px (0.0dp)
+ hotseatQsbSpace: 0.0px (0.0dp)
+ hotseatQsbHeight: 0.0px (0.0dp)
+ springLoadedHotseatBarTopMarginPx: 116.0px (44.190475dp)
+ getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
+ getHotseatLayoutPadding(context).bottom: 108.0px (41.142857dp)
+ getHotseatLayoutPadding(context).left: 113.0px (43.04762dp)
+ getHotseatLayoutPadding(context).right: 113.0px (43.04762dp)
+ numShownHotseatIcons: 6
+ hotseatBorderSpace: 0.0px (0.0dp)
+ isQsbInline: false
+ hotseatQsbWidth: 0.0px (0.0dp)
+ isTaskbarPresent:false
+ isTaskbarPresentInApps:true
+ taskbarHeight: 0.0px (0.0dp)
+ stashedTaskbarHeight: 0.0px (0.0dp)
+ taskbarBottomMargin: 0.0px (0.0dp)
+ taskbarIconSize: 0.0px (0.0dp)
+ desiredWorkspaceHorizontalMarginPx: 21.0px (8.0dp)
+ workspacePadding.left: 21.0px (8.0dp)
+ workspacePadding.top: 30.0px (11.428572dp)
+ workspacePadding.right: 21.0px (8.0dp)
+ workspacePadding.bottom: 330.0px (125.71429dp)
+ iconScale: 1.0px (0.3809524dp)
+ cellScaleToFit : 1.0px (0.3809524dp)
+ extraSpace: 498.0px (189.71428dp)
+ unscaled extraSpace: 498.0px (189.71428dp)
+ maxEmptySpace: 0.0px (0.0dp)
+ workspaceTopPadding: 0.0px (0.0dp)
+ workspaceBottomPadding: 0.0px (0.0dp)
+ overviewTaskMarginPx: 0.0px (0.0dp)
+ overviewTaskIconSizePx: 0.0px (0.0dp)
+ overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
+ overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
+ overviewActionsTopMarginPx: 0.0px (0.0dp)
+ overviewActionsHeight: 0.0px (0.0dp)
+ overviewActionsClaimedSpaceBelow: 0.0px (0.0dp)
+ overviewActionsButtonSpacing: 0.0px (0.0dp)
+ overviewPageSpacing: 0.0px (0.0dp)
+ overviewRowSpacing: 0.0px (0.0dp)
+ overviewGridSideMargin: 0.0px (0.0dp)
+ dropTargetBarTopMarginPx: 0.0px (0.0dp)
+ dropTargetBarSizePx: 147.0px (56.0dp)
+ dropTargetBarBottomMarginPx: 42.0px (16.0dp)
+ getCellLayoutSpringLoadShrunkTop(): 299.0px (113.90476dp)
+ getCellLayoutSpringLoadShrunkBottom(): 1457.0px (555.0476dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+ getWorkspaceSpringLoadScale(): 0.8452555px (0.32200208dp)
+ getCellLayoutHeight(): 1370.0px (521.9048dp)
+ getCellLayoutWidth(): 1083.0px (412.57144dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
index 3169f41..fb392a8 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 42.0px (16.0dp)
allAppsLeftRightMargin: 1.0px (0.3809524dp)
hotseatBarSizePx: 267.0px (101.71429dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 159.0px (60.57143dp)
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 84.0px (32.0dp)
getCellLayoutSpringLoadShrunkTop(): 364.0px (138.66667dp)
getCellLayoutSpringLoadShrunkBottom(): 1773.0px (675.4286dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.81871px (0.31188953dp)
getCellLayoutHeight(): 1721.0px (655.619dp)
getCellLayoutWidth(): 899.0px (342.4762dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
index 9d3d7bc..2c4b3c3 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 42.0px (16.0dp)
allAppsLeftRightMargin: 1.0px (0.3809524dp)
hotseatBarSizePx: 267.0px (101.71429dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 159.0px (60.57143dp)
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -123,7 +124,7 @@
dropTargetBarBottomMarginPx: 84.0px (32.0dp)
getCellLayoutSpringLoadShrunkTop(): 364.0px (138.66667dp)
getCellLayoutSpringLoadShrunkBottom(): 1773.0px (675.4286dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.81871px (0.31188953dp)
getCellLayoutHeight(): 1721.0px (655.619dp)
getCellLayoutWidth(): 899.0px (342.4762dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button_decoupleDepth.txt
new file mode 100644
index 0000000..e7b72f2
--- /dev/null
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button_decoupleDepth.txt
@@ -0,0 +1,130 @@
+DeviceProfile:
+ 1 dp = 2.625 px
+ isTablet:true
+ isPhone:false
+ transposeLayoutWithOrientation:false
+ isGestureMode:false
+ isLandscape:false
+ isMultiWindowMode:false
+ isTwoPanels:true
+ isLeftRightSplit:false
+ windowX: 0.0px (0.0dp)
+ windowY: 0.0px (0.0dp)
+ widthPx: 1840.0px (700.9524dp)
+ heightPx: 2208.0px (841.1429dp)
+ availableWidthPx: 1840.0px (700.9524dp)
+ availableHeightPx: 2075.0px (790.4762dp)
+ mInsets.left: 0.0px (0.0dp)
+ mInsets.top: 133.0px (50.666668dp)
+ mInsets.right: 0.0px (0.0dp)
+ mInsets.bottom: 0.0px (0.0dp)
+ aspectRatio:1.2
+ isResponsiveGrid:false
+ isScalableGrid:false
+ inv.numRows: 4
+ inv.numColumns: 4
+ inv.numSearchContainerColumns: 4
+ minCellSize: PointF(0.0, 0.0)dp
+ cellWidthPx: 154.0px (58.666668dp)
+ cellHeightPx: 218.0px (83.04762dp)
+ getCellSize().x: 224.0px (85.333336dp)
+ getCellSize().y: 430.0px (163.80952dp)
+ cellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)
+ cellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)
+ cellLayoutPaddingPx.left: 0.0px (0.0dp)
+ cellLayoutPaddingPx.top: 0.0px (0.0dp)
+ cellLayoutPaddingPx.right: 0.0px (0.0dp)
+ cellLayoutPaddingPx.bottom: 0.0px (0.0dp)
+ iconSizePx: 141.0px (53.714287dp)
+ iconTextSizePx: 34.0px (12.952381dp)
+ iconDrawablePaddingPx: 13.0px (4.952381dp)
+ numFolderRows: 3
+ numFolderColumns: 4
+ folderCellWidthPx: 189.0px (72.0dp)
+ folderCellHeightPx: 219.0px (83.42857dp)
+ folderChildIconSizePx: 141.0px (53.714287dp)
+ folderChildTextSizePx: 34.0px (12.952381dp)
+ folderChildDrawablePaddingPx: 5.0px (1.9047619dp)
+ folderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)
+ folderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)
+ folderContentPaddingLeftRight: 21.0px (8.0dp)
+ folderTopPadding: 63.0px (24.0dp)
+ folderFooterHeight: 147.0px (56.0dp)
+ bottomSheetTopPadding: 133.0px (50.666668dp)
+ bottomSheetOpenDuration: 500
+ bottomSheetCloseDuration: 500
+ bottomSheetWorkspaceScale: 0.97
+ bottomSheetDepth: 0.3
+ allAppsShiftRange: 2208.0px (841.1429dp)
+ allAppsOpenDuration: 500
+ allAppsCloseDuration: 500
+ allAppsIconSizePx: 141.0px (53.714287dp)
+ allAppsIconTextSizePx: 34.0px (12.952381dp)
+ allAppsIconDrawablePaddingPx: 21.0px (8.0dp)
+ allAppsCellHeightPx: 361.0px (137.5238dp)
+ allAppsCellWidthPx: 183.0px (69.71429dp)
+ allAppsBorderSpacePxX: 42.0px (16.0dp)
+ allAppsBorderSpacePxY: 42.0px (16.0dp)
+ numShownAllAppsColumns: 8
+ allAppsPadding.top: 133.0px (50.666668dp)
+ allAppsPadding.left: 42.0px (16.0dp)
+ allAppsPadding.right: 42.0px (16.0dp)
+ allAppsLeftRightMargin: 1.0px (0.3809524dp)
+ hotseatBarSizePx: 267.0px (101.71429dp)
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
+ hotseatCellHeightPx: 159.0px (60.57143dp)
+ hotseatBarBottomSpacePx: 126.0px (48.0dp)
+ mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
+ mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ hotseatBarEndOffset: 0.0px (0.0dp)
+ hotseatQsbSpace: 0.0px (0.0dp)
+ hotseatQsbHeight: 0.0px (0.0dp)
+ springLoadedHotseatBarTopMarginPx: 168.0px (64.0dp)
+ getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
+ getHotseatLayoutPadding(context).bottom: 108.0px (41.142857dp)
+ getHotseatLayoutPadding(context).left: 98.0px (37.333332dp)
+ getHotseatLayoutPadding(context).right: 98.0px (37.333332dp)
+ numShownHotseatIcons: 6
+ hotseatBorderSpace: 0.0px (0.0dp)
+ isQsbInline: false
+ hotseatQsbWidth: 0.0px (0.0dp)
+ isTaskbarPresent:false
+ isTaskbarPresentInApps:true
+ taskbarHeight: 0.0px (0.0dp)
+ stashedTaskbarHeight: 0.0px (0.0dp)
+ taskbarBottomMargin: 0.0px (0.0dp)
+ taskbarIconSize: 0.0px (0.0dp)
+ desiredWorkspaceHorizontalMarginPx: 21.0px (8.0dp)
+ workspacePadding.left: 21.0px (8.0dp)
+ workspacePadding.top: 24.0px (9.142858dp)
+ workspacePadding.right: 21.0px (8.0dp)
+ workspacePadding.bottom: 330.0px (125.71429dp)
+ iconScale: 1.0px (0.3809524dp)
+ cellScaleToFit : 1.0px (0.3809524dp)
+ extraSpace: 849.0px (323.42856dp)
+ unscaled extraSpace: 849.0px (323.42856dp)
+ maxEmptySpace: 0.0px (0.0dp)
+ workspaceTopPadding: 0.0px (0.0dp)
+ workspaceBottomPadding: 0.0px (0.0dp)
+ overviewTaskMarginPx: 0.0px (0.0dp)
+ overviewTaskIconSizePx: 0.0px (0.0dp)
+ overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
+ overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
+ overviewActionsTopMarginPx: 0.0px (0.0dp)
+ overviewActionsHeight: 0.0px (0.0dp)
+ overviewActionsClaimedSpaceBelow: 0.0px (0.0dp)
+ overviewActionsButtonSpacing: 0.0px (0.0dp)
+ overviewPageSpacing: 0.0px (0.0dp)
+ overviewRowSpacing: 0.0px (0.0dp)
+ overviewGridSideMargin: 0.0px (0.0dp)
+ dropTargetBarTopMarginPx: 0.0px (0.0dp)
+ dropTargetBarSizePx: 147.0px (56.0dp)
+ dropTargetBarBottomMarginPx: 84.0px (32.0dp)
+ getCellLayoutSpringLoadShrunkTop(): 364.0px (138.66667dp)
+ getCellLayoutSpringLoadShrunkBottom(): 1773.0px (675.4286dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+ getWorkspaceSpringLoadScale(): 0.81871px (0.31188953dp)
+ getCellLayoutHeight(): 1721.0px (655.619dp)
+ getCellLayoutWidth(): 899.0px (342.4762dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait_decoupleDepth.txt
new file mode 100644
index 0000000..eae50f1
--- /dev/null
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait_decoupleDepth.txt
@@ -0,0 +1,130 @@
+DeviceProfile:
+ 1 dp = 2.625 px
+ isTablet:true
+ isPhone:false
+ transposeLayoutWithOrientation:false
+ isGestureMode:true
+ isLandscape:false
+ isMultiWindowMode:false
+ isTwoPanels:true
+ isLeftRightSplit:false
+ windowX: 0.0px (0.0dp)
+ windowY: 0.0px (0.0dp)
+ widthPx: 1840.0px (700.9524dp)
+ heightPx: 2208.0px (841.1429dp)
+ availableWidthPx: 1840.0px (700.9524dp)
+ availableHeightPx: 2075.0px (790.4762dp)
+ mInsets.left: 0.0px (0.0dp)
+ mInsets.top: 133.0px (50.666668dp)
+ mInsets.right: 0.0px (0.0dp)
+ mInsets.bottom: 0.0px (0.0dp)
+ aspectRatio:1.2
+ isResponsiveGrid:false
+ isScalableGrid:false
+ inv.numRows: 4
+ inv.numColumns: 4
+ inv.numSearchContainerColumns: 4
+ minCellSize: PointF(0.0, 0.0)dp
+ cellWidthPx: 154.0px (58.666668dp)
+ cellHeightPx: 218.0px (83.04762dp)
+ getCellSize().x: 224.0px (85.333336dp)
+ getCellSize().y: 430.0px (163.80952dp)
+ cellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)
+ cellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)
+ cellLayoutPaddingPx.left: 0.0px (0.0dp)
+ cellLayoutPaddingPx.top: 0.0px (0.0dp)
+ cellLayoutPaddingPx.right: 0.0px (0.0dp)
+ cellLayoutPaddingPx.bottom: 0.0px (0.0dp)
+ iconSizePx: 141.0px (53.714287dp)
+ iconTextSizePx: 34.0px (12.952381dp)
+ iconDrawablePaddingPx: 13.0px (4.952381dp)
+ numFolderRows: 3
+ numFolderColumns: 4
+ folderCellWidthPx: 189.0px (72.0dp)
+ folderCellHeightPx: 219.0px (83.42857dp)
+ folderChildIconSizePx: 141.0px (53.714287dp)
+ folderChildTextSizePx: 34.0px (12.952381dp)
+ folderChildDrawablePaddingPx: 5.0px (1.9047619dp)
+ folderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)
+ folderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)
+ folderContentPaddingLeftRight: 21.0px (8.0dp)
+ folderTopPadding: 63.0px (24.0dp)
+ folderFooterHeight: 147.0px (56.0dp)
+ bottomSheetTopPadding: 133.0px (50.666668dp)
+ bottomSheetOpenDuration: 500
+ bottomSheetCloseDuration: 500
+ bottomSheetWorkspaceScale: 0.97
+ bottomSheetDepth: 0.3
+ allAppsShiftRange: 2208.0px (841.1429dp)
+ allAppsOpenDuration: 500
+ allAppsCloseDuration: 500
+ allAppsIconSizePx: 141.0px (53.714287dp)
+ allAppsIconTextSizePx: 34.0px (12.952381dp)
+ allAppsIconDrawablePaddingPx: 21.0px (8.0dp)
+ allAppsCellHeightPx: 361.0px (137.5238dp)
+ allAppsCellWidthPx: 183.0px (69.71429dp)
+ allAppsBorderSpacePxX: 42.0px (16.0dp)
+ allAppsBorderSpacePxY: 42.0px (16.0dp)
+ numShownAllAppsColumns: 8
+ allAppsPadding.top: 133.0px (50.666668dp)
+ allAppsPadding.left: 42.0px (16.0dp)
+ allAppsPadding.right: 42.0px (16.0dp)
+ allAppsLeftRightMargin: 1.0px (0.3809524dp)
+ hotseatBarSizePx: 267.0px (101.71429dp)
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
+ hotseatCellHeightPx: 159.0px (60.57143dp)
+ hotseatBarBottomSpacePx: 126.0px (48.0dp)
+ mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
+ mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ hotseatBarEndOffset: 0.0px (0.0dp)
+ hotseatQsbSpace: 0.0px (0.0dp)
+ hotseatQsbHeight: 0.0px (0.0dp)
+ springLoadedHotseatBarTopMarginPx: 168.0px (64.0dp)
+ getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
+ getHotseatLayoutPadding(context).bottom: 108.0px (41.142857dp)
+ getHotseatLayoutPadding(context).left: 98.0px (37.333332dp)
+ getHotseatLayoutPadding(context).right: 98.0px (37.333332dp)
+ numShownHotseatIcons: 6
+ hotseatBorderSpace: 0.0px (0.0dp)
+ isQsbInline: false
+ hotseatQsbWidth: 0.0px (0.0dp)
+ isTaskbarPresent:false
+ isTaskbarPresentInApps:true
+ taskbarHeight: 0.0px (0.0dp)
+ stashedTaskbarHeight: 0.0px (0.0dp)
+ taskbarBottomMargin: 0.0px (0.0dp)
+ taskbarIconSize: 0.0px (0.0dp)
+ desiredWorkspaceHorizontalMarginPx: 21.0px (8.0dp)
+ workspacePadding.left: 21.0px (8.0dp)
+ workspacePadding.top: 24.0px (9.142858dp)
+ workspacePadding.right: 21.0px (8.0dp)
+ workspacePadding.bottom: 330.0px (125.71429dp)
+ iconScale: 1.0px (0.3809524dp)
+ cellScaleToFit : 1.0px (0.3809524dp)
+ extraSpace: 849.0px (323.42856dp)
+ unscaled extraSpace: 849.0px (323.42856dp)
+ maxEmptySpace: 0.0px (0.0dp)
+ workspaceTopPadding: 0.0px (0.0dp)
+ workspaceBottomPadding: 0.0px (0.0dp)
+ overviewTaskMarginPx: 0.0px (0.0dp)
+ overviewTaskIconSizePx: 0.0px (0.0dp)
+ overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
+ overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
+ overviewActionsTopMarginPx: 0.0px (0.0dp)
+ overviewActionsHeight: 0.0px (0.0dp)
+ overviewActionsClaimedSpaceBelow: 0.0px (0.0dp)
+ overviewActionsButtonSpacing: 0.0px (0.0dp)
+ overviewPageSpacing: 0.0px (0.0dp)
+ overviewRowSpacing: 0.0px (0.0dp)
+ overviewGridSideMargin: 0.0px (0.0dp)
+ dropTargetBarTopMarginPx: 0.0px (0.0dp)
+ dropTargetBarSizePx: 147.0px (56.0dp)
+ dropTargetBarBottomMarginPx: 84.0px (32.0dp)
+ getCellLayoutSpringLoadShrunkTop(): 364.0px (138.66667dp)
+ getCellLayoutSpringLoadShrunkBottom(): 1773.0px (675.4286dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+ getWorkspaceSpringLoadScale(): 0.81871px (0.31188953dp)
+ getCellLayoutHeight(): 1721.0px (655.619dp)
+ getCellLayoutWidth(): 899.0px (342.4762dp)
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS b/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
index 02e8ebc..a818d5e 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
@@ -2,3 +2,4 @@
[email protected]
[email protected]
[email protected]
[email protected]
\ No newline at end of file
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index bc3a136..59d0de6 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -120,6 +120,8 @@
public static final String REQUEST_CLEAR_DATA = "clear-data";
public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names";
public static final String REQUEST_IS_TABLET = "is-tablet";
+ public static final String REQUEST_IS_PREDICTIVE_BACK_SWIPE_ENABLED =
+ "is-predictive-back-swipe-enabled";
public static final String REQUEST_ENABLE_TASKBAR_NAVBAR_UNIFICATION =
"enable-taskbar-navbar-unification";
public static final String REQUEST_NUM_ALL_APPS_COLUMNS = "num-all-apps-columns";
@@ -167,26 +169,21 @@
public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
- public static final String TWO_NEXUS_LAUNCHER_ACTIVITY_WHILE_UNLOCKING = "b/273347463";
- public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
public static final String ICON_MISSING = "b/282963545";
- public static final String OVERVIEW_OVER_HOME = "b/279059025";
public static final String UIOBJECT_STALE_ELEMENT = "b/319501259";
- public static final String GET_FROM_RECENTS_FAILURE = "b/321775748";
- public static final String SUCCESSFUL_GESTURE_MISMATCH_EVENTS = "b/324940434";
public static final String TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE = "b/326908466";
- public static final String TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE = "b/326073471";
-
- public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
- public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
- public static final String REQUEST_IS_EMULATE_DISPLAY_RUNNING = "is-emulate-display-running";
- public static final String REQUEST_EMULATE_PRINT_DEVICE = "emulate-print-device";
+ public static final String WIDGET_CONFIG_NULL_EXTRA_INTENT = "b/324419890";
+ public static final String OVERVIEW_SELECT_TOOLTIP_MISALIGNED = "b/332485341";
public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview";
+ public static final String REQUEST_FLAG_ENABLE_APP_PAIRS = "enable-app-pairs";
public static final String REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED =
"unstash-bubble-bar-if-stashed";
+ public static final String REQUEST_INJECT_FAKE_TRACKPAD = "inject-fake-trackpad";
+ public static final String REQUEST_EJECT_FAKE_TRACKPAD = "eject-fake-trackpad";
+
/** Logs {@link Log#d(String, String)} if {@link #sDebugTracing} is true. */
public static void testLogD(String tag, String message) {
if (!sDebugTracing) {
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index ffcf83f..8770859 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -38,17 +38,18 @@
import com.android.launcher3.util.window.CachedDisplayInfo
import com.android.launcher3.util.window.WindowManagerProxy
import com.google.common.truth.Truth
-import org.junit.Rule
-import org.mockito.kotlin.any
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.whenever
import java.io.BufferedReader
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import kotlin.math.max
import kotlin.math.min
+import org.junit.Rule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
/**
* This is an abstract class for DeviceProfile tests that create an InvariantDeviceProfile based on
@@ -56,7 +57,7 @@
*
* For an implementation that mocks InvariantDeviceProfile, use [FakeInvariantDeviceProfileTest]
*/
-@AllowedDevices(allowed = [DeviceProduct.CF_PHONE])
+@AllowedDevices(allowed = [DeviceProduct.CF_PHONE, DeviceProduct.ROBOLECTRIC])
@IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD)
abstract class AbstractDeviceProfileTest {
protected val testContext: Context = InstrumentationRegistry.getInstrumentation().context
@@ -286,6 +287,9 @@
.thenReturn(
if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS
)
+ doReturn(WindowManagerProxy.INSTANCE[runningContext].isTaskbarDrawnInProcess)
+ .whenever(windowManagerProxy)
+ .isTaskbarDrawnInProcess()
val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat()
val config =
@@ -296,18 +300,13 @@
smallestScreenWidthDp = min(screenWidthDp, screenHeightDp)
}
val configurationContext = runningContext.createConfigurationContext(config)
- context =
- SandboxContext(
- configurationContext,
- DisplayController.INSTANCE,
- WindowManagerProxy.INSTANCE,
- LauncherPrefs.INSTANCE
- )
+ context = SandboxContext(configurationContext)
context.putObject(DisplayController.INSTANCE, displayController)
context.putObject(WindowManagerProxy.INSTANCE, windowManagerProxy)
context.putObject(LauncherPrefs.INSTANCE, launcherPrefs)
whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING)).thenReturn(false)
+ whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true)
val info = spy(DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache))
whenever(displayController.info).thenReturn(info)
whenever(info.isTransientTaskbar).thenReturn(isGestureMode)
@@ -342,4 +341,9 @@
protected fun Int.dpToPx(): Int {
return ResourceUtils.pxFromDp(this.toFloat(), context!!.resources.displayMetrics)
}
+
+ protected fun String.xmlToId(): Int {
+ val context = InstrumentationRegistry.getInstrumentation().context
+ return context.resources.getIdentifier(this, "xml", context.packageName)
+ }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractFloatingViewHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractFloatingViewHelperTest.kt
new file mode 100644
index 0000000..5344d5c
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractFloatingViewHelperTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.view.View
+import com.android.launcher3.dragndrop.DragLayer
+import com.android.launcher3.views.ActivityContext
+import org.junit.Before
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
+import org.mockito.kotlin.whenever
+
+/** Test for AbstractFloatingViewHelper */
+class AbstractFloatingViewHelperTest {
+ private val activityContext: ActivityContext = mock()
+ private val dragLayer: DragLayer = mock()
+ private val view: View = mock()
+ private val folderView: AbstractFloatingView = mock()
+ private val taskMenuView: AbstractFloatingView = mock()
+ private val abstractFloatingViewHelper = AbstractFloatingViewHelper()
+
+ @Before
+ fun setup() {
+ whenever(activityContext.dragLayer).thenReturn(dragLayer)
+ whenever(dragLayer.childCount).thenReturn(3)
+ whenever(dragLayer.getChildAt(0)).thenReturn(view)
+ whenever(dragLayer.getChildAt(1)).thenReturn(folderView)
+ whenever(dragLayer.getChildAt(2)).thenReturn(taskMenuView)
+ whenever(folderView.isOfType(any())).thenAnswer {
+ (it.getArgument<Int>(0) and AbstractFloatingView.TYPE_FOLDER) != 0
+ }
+ whenever(taskMenuView.isOfType(any())).thenAnswer {
+ (it.getArgument<Int>(0) and AbstractFloatingView.TYPE_TASK_MENU) != 0
+ }
+ }
+
+ @Test
+ fun closeOpenViews_all() {
+ abstractFloatingViewHelper.closeOpenViews(
+ activityContext,
+ true,
+ AbstractFloatingView.TYPE_ALL
+ )
+
+ // b/343530737
+ verifyNoMoreInteractions(view)
+ verify(folderView).close(true)
+ verify(taskMenuView).close(true)
+ }
+
+ @Test
+ fun closeOpenViews_taskMenu() {
+ abstractFloatingViewHelper.closeOpenViews(
+ activityContext,
+ true,
+ AbstractFloatingView.TYPE_TASK_MENU
+ )
+
+ // b/343530737
+ verifyNoMoreInteractions(view)
+ verify(folderView, never()).close(any())
+ verify(taskMenuView).close(true)
+ }
+
+ @Test
+ fun closeOpenViews_other() {
+ abstractFloatingViewHelper.closeOpenViews(
+ activityContext,
+ true,
+ AbstractFloatingView.TYPE_PIN_IME_POPUP
+ )
+
+ // b/343530737
+ verifyNoMoreInteractions(view)
+ verify(folderView, never()).close(any())
+ verify(taskMenuView, never()).close(any())
+ }
+
+ @Test
+ fun closeOpenViews_both_animationOff() {
+ abstractFloatingViewHelper.closeOpenViews(
+ activityContext,
+ false,
+ AbstractFloatingView.TYPE_FOLDER or AbstractFloatingView.TYPE_TASK_MENU
+ )
+
+ // b/343530737
+ verifyNoMoreInteractions(view)
+ verify(folderView).close(false)
+ verify(taskMenuView).close(false)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/AppFilterTest.kt b/tests/multivalentTests/src/com/android/launcher3/AppFilterTest.kt
new file mode 100644
index 0000000..f1c6343
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/AppFilterTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.res.Resources
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AppFilterTest {
+
+ @Mock private lateinit var mockContext: Context
+
+ @Mock // Mock the Resources object as well
+ private lateinit var mockResources: Resources
+
+ private lateinit var appFilter: AppFilter
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ `when`(mockContext.resources).thenReturn(mockResources) // Link the context and resources
+ `when`(mockResources.getStringArray(R.array.filtered_components))
+ .thenReturn(arrayOf("com.example.app1/Activity1"))
+ appFilter = AppFilter(mockContext)
+ }
+
+ @Test
+ fun shouldShowApp_notFiltered_returnsTrue() {
+ val appToShow = ComponentName("com.example.app2", "Activity2")
+ assertThat(appFilter.shouldShowApp(appToShow)).isTrue()
+ }
+
+ @Test
+ fun shouldShowApp_filtered_returnsFalse() {
+ val appToHide = ComponentName("com.example.app1", "Activity1")
+ assertThat(appFilter.shouldShowApp(appToHide)).isFalse()
+ }
+}
diff --git a/tests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
diff --git a/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt b/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
new file mode 100644
index 0000000..b04bcca
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.content.ComponentName
+import android.content.ContentValues
+import android.database.sqlite.SQLiteDatabase
+import android.os.Process.myUserHandle
+import android.os.UserHandle
+import android.util.Xml
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback
+import com.android.launcher3.AutoInstallsLayout.SourceResources
+import com.android.launcher3.AutoInstallsLayout.TAG_WORKSPACE
+import com.android.launcher3.AutoInstallsLayout.USER_TYPE_WORK
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_PROVIDER
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.LauncherSettings.Favorites.INTENT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID
+import com.android.launcher3.LauncherSettings.Favorites.SPANX
+import com.android.launcher3.LauncherSettings.Favorites.SPANY
+import com.android.launcher3.LauncherSettings.Favorites._ID
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.ApiWrapper
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.LauncherLayoutBuilder
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.TestUtil
+import com.android.launcher3.util.UserIconInfo
+import com.android.launcher3.util.UserIconInfo.TYPE_MAIN
+import com.android.launcher3.util.UserIconInfo.TYPE_WORK
+import com.android.launcher3.widget.LauncherWidgetHolder
+import com.google.common.truth.Truth.assertThat
+import java.io.StringReader
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+/** Tests for [AutoInstallsLayout] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AutoInstallsLayoutTest {
+
+ lateinit var modelHelper: LauncherModelHelper
+ lateinit var targetContext: SandboxModelContext
+
+ lateinit var callback: MyCallback
+
+ @Mock lateinit var widgetHolder: LauncherWidgetHolder
+ @Mock lateinit var db: SQLiteDatabase
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ modelHelper = LauncherModelHelper()
+ targetContext = modelHelper.sandboxContext
+ callback = MyCallback()
+ }
+
+ @After
+ fun tearDown() {
+ modelHelper.destroy()
+ }
+
+ @Test
+ fun pending_icon_added_on_home() {
+ LauncherLayoutBuilder()
+ .atWorkspace(1, 1, 0)
+ .putApp("p1", "c1")
+ .toAutoInstallsLayout()
+ .loadLayout(db)
+
+ assertThat(callback.items.size).isEqualTo(1)
+ assertThat(callback.items[0][ITEM_TYPE]).isEqualTo(ITEM_TYPE_APPLICATION)
+ assertThat(callback.items[0][INTENT])
+ .isEqualTo(AppInfo.makeLaunchIntent(ComponentName("p1", "c1")).toUri(0))
+ assertThat(callback.items[0][CONTAINER]).isEqualTo(CONTAINER_DESKTOP)
+ assertThat(callback.items[0].containsKey(PROFILE_ID)).isFalse()
+ }
+
+ @Test
+ fun pending_icon_added_on_hotseat() {
+ LauncherLayoutBuilder()
+ .atHotseat(1)
+ .putApp("p1", "c1")
+ .toAutoInstallsLayout()
+ .loadLayout(db)
+
+ assertThat(callback.items.size).isEqualTo(1)
+ assertThat(callback.items[0][ITEM_TYPE]).isEqualTo(ITEM_TYPE_APPLICATION)
+ assertThat(callback.items[0][CONTAINER]).isEqualTo(CONTAINER_HOTSEAT)
+ }
+
+ @Test
+ fun widget_added_to_home() {
+ LauncherLayoutBuilder()
+ .atWorkspace(1, 1, 0)
+ .putWidget("p1", "c1", 2, 3)
+ .toAutoInstallsLayout()
+ .loadLayout(db)
+
+ assertThat(callback.items.size).isEqualTo(1)
+ assertThat(callback.items[0][ITEM_TYPE]).isEqualTo(ITEM_TYPE_APPWIDGET)
+ assertThat(callback.items[0][CONTAINER]).isEqualTo(CONTAINER_DESKTOP)
+ assertThat(callback.items[0][APPWIDGET_PROVIDER])
+ .isEqualTo(ComponentName("p1", "c1").flattenToString())
+ assertThat(callback.items[0][SPANX]).isEqualTo(2.toString())
+ assertThat(callback.items[0][SPANY]).isEqualTo(3.toString())
+ }
+
+ @Test
+ fun items_added_to_folder() {
+ LauncherLayoutBuilder()
+ .atHotseat(1)
+ .putFolder("Test")
+ .addApp("p1", "c")
+ .addApp("p2", "c")
+ .addApp("p3", "c")
+ .build()
+ .toAutoInstallsLayout()
+ .loadLayout(db)
+
+ assertThat(callback.items.size).isEqualTo(4)
+ assertThat(callback.items[0][ITEM_TYPE]).isEqualTo(ITEM_TYPE_FOLDER)
+ assertThat(callback.items[0][CONTAINER]).isEqualTo(CONTAINER_HOTSEAT)
+
+ val folderId = callback.items[0][_ID]
+ assertThat(callback.items[1][CONTAINER]).isEqualTo(folderId)
+ assertThat(callback.items[2][CONTAINER]).isEqualTo(folderId)
+ assertThat(callback.items[3][CONTAINER]).isEqualTo(folderId)
+ }
+
+ @Test
+ fun work_item_added_to_home() {
+ val apiWrapperMock = spy(ApiWrapper.INSTANCE[targetContext])
+ targetContext.putObject(ApiWrapper.INSTANCE, apiWrapperMock)
+ doReturn(
+ mapOf(
+ myUserHandle() to UserIconInfo(myUserHandle(), TYPE_MAIN, 0),
+ UserHandle.of(20) to UserIconInfo(UserHandle.of(20), TYPE_WORK, 20),
+ )
+ )
+ .whenever(apiWrapperMock)
+ .queryAllUsers()
+
+ val cache = UserCache.getInstance(targetContext)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ assertThat(cache.userProfiles.size).isEqualTo(2)
+ }
+
+ LauncherLayoutBuilder()
+ .atWorkspace(1, 1, 0)
+ .putApp("p1", "c1", USER_TYPE_WORK)
+ .toAutoInstallsLayout()
+ .loadLayout(db)
+
+ assertThat(callback.items.size).isEqualTo(1)
+ assertThat(callback.items[0][ITEM_TYPE]).isEqualTo(ITEM_TYPE_APPLICATION)
+ assertThat(callback.items[0][INTENT])
+ .isEqualTo(AppInfo.makeLaunchIntent(ComponentName("p1", "c1")).toUri(0))
+ assertThat(callback.items[0][CONTAINER]).isEqualTo(CONTAINER_DESKTOP)
+ assertThat(callback.items[0][PROFILE_ID]).isEqualTo(20)
+ }
+
+ private fun LauncherLayoutBuilder.toAutoInstallsLayout() =
+ AutoInstallsLayout(
+ targetContext,
+ widgetHolder,
+ callback,
+ SourceResources.wrap(targetContext.resources),
+ { Xml.newPullParser().also { it.setInput(StringReader(build())) } },
+ TAG_WORKSPACE
+ )
+
+ class MyCallback : LayoutParserCallback {
+
+ val items = ArrayList<ContentValues>()
+
+ override fun generateNewItemId() = items.size
+
+ override fun insertAndCheck(db: SQLiteDatabase?, values: ContentValues): Int {
+ val id = values[_ID]
+ items.add(ContentValues(values))
+ return if (id is Int) id else 0
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/DeleteDropTargetTest.kt b/tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/DeleteDropTargetTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
similarity index 96%
rename from tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 251a401..0538870 100644
--- a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -46,7 +46,7 @@
@IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD)
abstract class FakeInvariantDeviceProfileTest {
- protected var context: Context? = null
+ protected lateinit var context: Context
protected var inv: InvariantDeviceProfile? = null
protected val info: Info = mock()
protected var windowBounds: WindowBounds? = null
@@ -157,7 +157,6 @@
numDatabaseHotseatIcons = 4
- hotseatColumnSpan = IntArray(4) { 4 }
hotseatBarBottomSpace = FloatArray(4) { 48f }
hotseatQsbSpace = FloatArray(4) { 36f }
@@ -240,7 +239,6 @@
numDatabaseHotseatIcons = 6
- hotseatColumnSpan = intArrayOf(6, 4, 6, 6)
hotseatBarBottomSpace = floatArrayOf(36f, 40f, 36f, 36f)
hotseatQsbSpace = FloatArray(4) { 32f }
@@ -260,7 +258,9 @@
protected fun initializeVarsForTwoPanel(
isLandscape: Boolean = false,
- isGestureMode: Boolean = true
+ isGestureMode: Boolean = true,
+ rows: Int = 4,
+ cols: Int = 4,
) {
val (x, y) = if (isLandscape) Pair(2208, 1840) else Pair(1840, 2208)
@@ -276,9 +276,9 @@
inv =
InvariantDeviceProfile().apply {
- numRows = 4
- numColumns = 4
- numSearchContainerColumns = 4
+ numRows = rows
+ numColumns = cols
+ numSearchContainerColumns = cols
iconSize = floatArrayOf(60f, 52f, 52f, 60f)
iconTextSize = floatArrayOf(14f, 14f, 12f, 14f)
@@ -319,7 +319,6 @@
numDatabaseHotseatIcons = 6
- hotseatColumnSpan = IntArray(4) { 6 }
hotseatBarBottomSpace = floatArrayOf(48f, 48f, 36f, 20f)
hotseatQsbSpace = floatArrayOf(36f, 36f, 36f, 28f)
diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
similarity index 67%
rename from tests/src/com/android/launcher3/LauncherPrefsTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
index 88a430b..b813095 100644
--- a/tests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -25,8 +25,6 @@
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
-import org.junit.AfterClass
-import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,20 +46,6 @@
private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext }
private val launcherPrefs by lazy { LauncherPrefs.get(context) }
- companion object {
- @BeforeClass
- @JvmStatic
- fun setup() {
- moveStartupDataToDeviceProtectedStorageIsEnabled = true
- }
-
- @AfterClass
- @JvmStatic
- fun teardown() {
- moveStartupDataToDeviceProtectedStorageIsEnabled = false
- }
- }
-
@Test
fun has_keyMissingFromLauncherPrefs_returnsFalse() {
assertThat(launcherPrefs.has(TEST_BOOLEAN_ITEM)).isFalse()
@@ -223,31 +207,12 @@
}
@Test
- fun put_bootAwareItem_updatesEncryptedStorage() {
- val bootAwareItem =
- LauncherPrefs.backedUpItem(
- TEST_PREF_KEY,
- TEST_DEFAULT_VALUE,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
-
- val encryptedPrefs: SharedPreferences =
- context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
- encryptedPrefs.edit().remove(bootAwareItem.sharedPrefKey).commit()
-
- launcherPrefs.putSync(bootAwareItem.to(TEST_STRING_ITEM.defaultValue))
- assertThat(encryptedPrefs.contains(bootAwareItem.sharedPrefKey)).isTrue()
-
- launcherPrefs.removeSync(bootAwareItem)
- }
-
- @Test
fun remove_bootAwareItem_removesFromDeviceProtectedStorage() {
val bootAwareItem =
LauncherPrefs.backedUpItem(
TEST_PREF_KEY,
TEST_DEFAULT_VALUE,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ EncryptionType.DEVICE_PROTECTED
)
val bootAwarePrefs: SharedPreferences =
@@ -263,90 +228,4 @@
launcherPrefs.removeSync(bootAwareItem)
assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isFalse()
}
-
- @Test
- fun remove_bootAwareItem_removesFromEncryptedStorage() {
- val bootAwareItem =
- LauncherPrefs.backedUpItem(
- TEST_PREF_KEY,
- TEST_DEFAULT_VALUE,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
-
- val encryptedPrefs: SharedPreferences =
- context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
-
- encryptedPrefs
- .edit()
- .putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue)
- .commit()
-
- launcherPrefs.removeSync(bootAwareItem)
- assertThat(encryptedPrefs.contains(bootAwareItem.sharedPrefKey)).isFalse()
- }
-
- @Test
- fun migrate_bootAwareItemsToDeviceProtectedStorage_worksAsIntended() {
- val bootAwareItem =
- LauncherPrefs.backedUpItem(
- TEST_PREF_KEY,
- TEST_DEFAULT_VALUE,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
- launcherPrefs.removeSync(bootAwareItem)
-
- val bootAwarePrefs: SharedPreferences =
- context
- .createDeviceProtectedStorageContext()
- .getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE)
-
- if (bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)) {
- bootAwarePrefs.edit().remove(bootAwareItem.sharedPrefKey).commit()
- }
-
- val encryptedPrefs: SharedPreferences =
- context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
-
- encryptedPrefs
- .edit()
- .putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue)
- .commit()
-
- launcherPrefs.migrateStartupDataToDeviceProtectedStorage()
- assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isTrue()
-
- launcherPrefs.removeSync(bootAwareItem)
- }
-
- @Test
- fun migrate_onlyEncryptedItemsToDeviceProtectedStorage_doesNotHappen() {
- val onlyEncryptedItem =
- LauncherPrefs.backedUpItem(
- TEST_PREF_KEY + "_",
- TEST_DEFAULT_VALUE + "_",
- EncryptionType.ENCRYPTED
- )
-
- val bootAwarePrefs: SharedPreferences =
- context
- .createDeviceProtectedStorageContext()
- .getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE)
-
- if (bootAwarePrefs.contains(onlyEncryptedItem.sharedPrefKey)) {
- bootAwarePrefs.edit().remove(onlyEncryptedItem.sharedPrefKey).commit()
- }
-
- val encryptedPrefs: SharedPreferences =
- context.getSharedPreferences(onlyEncryptedItem.sharedPrefFile, Context.MODE_PRIVATE)
-
- encryptedPrefs
- .edit()
- .putString(onlyEncryptedItem.sharedPrefKey, onlyEncryptedItem.defaultValue)
- .commit()
-
- launcherPrefs.migrateStartupDataToDeviceProtectedStorage()
- assertThat(bootAwarePrefs.contains(onlyEncryptedItem.sharedPrefKey)).isFalse()
-
- encryptedPrefs.edit().remove(onlyEncryptedItem.sharedPrefKey).commit()
- }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/UtilitiesKtTest.kt b/tests/multivalentTests/src/com/android/launcher3/UtilitiesKtTest.kt
new file mode 100644
index 0000000..0d13e77
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/UtilitiesKtTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.content.Context
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER
+import com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER
+import com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree
+import com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UtilitiesKtTest {
+ val context: Context = InstrumentationRegistry.getInstrumentation().context
+
+ private val childView = TextView(context)
+
+ private val midView = LinearLayout(context).apply { addView(childView) }
+
+ private val rootView = LinearLayout(context).apply { addView(midView) }
+
+ @Test
+ fun set_clipChildren_false() {
+ assertThat(rootView.clipChildren).isTrue()
+ assertThat(midView.clipChildren).isTrue()
+
+ modifyAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER)
+
+ assertThat(rootView.clipChildren).isFalse()
+ assertThat(midView.clipChildren).isFalse()
+ }
+
+ @Test
+ fun restore_clipChildren_true() {
+ assertThat(rootView.clipChildren).isTrue()
+ assertThat(midView.clipChildren).isTrue()
+ modifyAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER)
+ assertThat(rootView.clipChildren).isFalse()
+ assertThat(midView.clipChildren).isFalse()
+
+ restoreAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER)
+
+ assertThat(rootView.clipChildren).isTrue()
+ assertThat(midView.clipChildren).isTrue()
+ }
+
+ @Test
+ fun restore_clipChildren_skipRestoreMidView() {
+ assertThat(rootView.clipChildren).isTrue()
+ assertThat(midView.clipChildren).isTrue()
+ rootView.clipChildren = false
+ modifyAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER)
+ assertThat(rootView.clipChildren).isFalse()
+ assertThat(midView.clipChildren).isFalse()
+
+ restoreAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER)
+
+ assertThat(rootView.clipChildren).isFalse()
+ assertThat(midView.clipChildren).isTrue()
+ }
+
+ @Test
+ fun set_clipToPadding_false() {
+ assertThat(rootView.clipToPadding).isTrue()
+ assertThat(midView.clipToPadding).isTrue()
+
+ modifyAttributesOnViewTree(childView, rootView, CLIP_TO_PADDING_FALSE_MODIFIER)
+
+ assertThat(rootView.clipToPadding).isFalse()
+ assertThat(midView.clipToPadding).isFalse()
+ }
+
+ @Test
+ fun restore_clipToPadding_true() {
+ assertThat(rootView.clipToPadding).isTrue()
+ assertThat(midView.clipToPadding).isTrue()
+ modifyAttributesOnViewTree(childView, rootView, CLIP_TO_PADDING_FALSE_MODIFIER)
+ assertThat(rootView.clipToPadding).isFalse()
+ assertThat(midView.clipToPadding).isFalse()
+
+ restoreAttributesOnViewTree(childView, rootView, CLIP_TO_PADDING_FALSE_MODIFIER)
+
+ assertThat(rootView.clipToPadding).isTrue()
+ assertThat(midView.clipToPadding).isTrue()
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt b/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt
new file mode 100644
index 0000000..60a4197
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.ActivityContextWrapper
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class UtilitiesTest {
+
+ private lateinit var mContext: Context
+
+ @Before
+ fun setUp() {
+ mContext = ActivityContextWrapper(getApplicationContext())
+ }
+
+ @Test
+ fun testIsPropertyEnabled() {
+ // This assumes the property "propertyName" is not enabled by default
+ assertFalse(Utilities.isPropertyEnabled("propertyName"))
+ }
+
+ @Test
+ fun testGetDescendantCoordRelativeToAncestor() {
+ val ancestor =
+ object : ViewGroup(mContext) {
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
+ }
+ val descendant = View(mContext)
+
+ descendant.x = 50f
+ descendant.y = 30f
+ descendant.scaleX = 2f
+ descendant.scaleY = 2f
+
+ ancestor.addView(descendant)
+
+ val coord = floatArrayOf(10f, 15f)
+ val scale =
+ Utilities.getDescendantCoordRelativeToAncestor(descendant, ancestor, coord, false)
+
+ assertEquals(2f, scale) // Expecting scale to be 2f
+ assertEquals(70f, coord[0])
+ assertEquals(60f, coord[1])
+ }
+
+ @Test
+ fun testRoundArray() {
+ val floatArray = floatArrayOf(1.2f, 3.7f, 5.5f)
+ val intArray = IntArray(3)
+ Utilities.roundArray(floatArray, intArray)
+ assertArrayEquals(intArrayOf(1, 4, 6), intArray)
+ }
+
+ @Test
+ fun testOffsetPoints() {
+ val points = floatArrayOf(1f, 2f, 3f, 4f)
+ Utilities.offsetPoints(points, 5f, 6f)
+
+ val expected = listOf(6f, 8f, 8f, 10f)
+ assertEquals(expected, points.toList())
+ }
+
+ @Test
+ fun testPointInView() {
+ val view = View(mContext)
+ view.layout(0, 0, 100, 100)
+
+ assertTrue(Utilities.pointInView(view, 50f, 50f, 0f)) // Inside view
+ assertFalse(Utilities.pointInView(view, -10f, -10f, 0f)) // Outside view
+ assertTrue(Utilities.pointInView(view, -5f, -5f, 10f)) // Inside slop
+ assertFalse(Utilities.pointInView(view, 115f, 115f, 10f)) // Outside slop
+ }
+}
diff --git a/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
similarity index 99%
rename from tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
index 423ca24..d2238ff 100644
--- a/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
@@ -98,7 +98,7 @@
when(mPrivateProfileManager.addPrivateSpaceHeader(any()))
.thenAnswer(answer(this::addPrivateSpaceHeader));
when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
- when(mPrivateProfileManager.splitIntoUserInstalledAndSystemApps())
+ when(mPrivateProfileManager.splitIntoUserInstalledAndSystemApps(any()))
.thenReturn(iteminfo -> iteminfo.componentName == null
|| !iteminfo.componentName.getPackageName()
.equals("com.android.launcher3.tests.camera"));
@@ -127,7 +127,7 @@
when(mPrivateProfileManager.addSystemAppsDivider(any()))
.thenAnswer(answer(this::addSystemAppsDivider));
when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
- when(mPrivateProfileManager.splitIntoUserInstalledAndSystemApps())
+ when(mPrivateProfileManager.splitIntoUserInstalledAndSystemApps(mContext))
.thenReturn(iteminfo -> iteminfo.componentName == null
|| !iteminfo.componentName.getPackageName()
.equals("com.android.launcher3.tests.camera"));
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
diff --git a/tests/src/com/android/launcher3/celllayout/CellPosMapperTest.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellPosMapperTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/CellPosMapperTest.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/CellPosMapperTest.java
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
similarity index 83%
rename from tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index dbb2715..0c3081f 100644
--- a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.celllayout;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -24,16 +22,13 @@
import android.content.Context;
-import androidx.test.uiautomator.UiDevice;
-
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
-import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ModelTestExtensions;
@@ -78,9 +73,8 @@
ContentWriter writer = new ContentWriter(mContext);
ItemInfo item = mItemsToSubmit.get(i).get();
- if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
- FolderInfo folderInfo = (FolderInfo) item;
- for (ItemInfo itemInfo : folderInfo.contents) {
+ if (item instanceof CollectionInfo ci) {
+ for (ItemInfo itemInfo : ci.getContents()) {
itemInfo.container = i;
containerItems.add(itemInfo);
}
@@ -106,15 +100,4 @@
runOnExecutorSync(MAIN_EXECUTOR, model::forceReload);
ModelTestExtensions.INSTANCE.loadModelSync(model);
}
-
- /**
- * Commits the transaction and waits for home load
- */
- public void commitAndLoadHome(LauncherInstrumentation inst) {
- commit();
-
- // Launch the home activity
- UiDevice.getInstance(getInstrumentation()).pressHome();
- inst.waitForLauncherInitialized();
- }
}
diff --git a/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
similarity index 97%
rename from tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
index 13dfd5e..c32461e 100644
--- a/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
@@ -22,6 +22,8 @@
import android.view.View
import androidx.core.view.get
import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
import com.android.launcher3.CellLayout
import com.android.launcher3.celllayout.board.CellLayoutBoard
import com.android.launcher3.celllayout.board.IconPoint
@@ -34,6 +36,7 @@
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
+import org.junit.runner.RunWith
private class HotseatReorderTestCase(
val startBoard: CellLayoutBoard,
@@ -44,6 +47,8 @@
}
}
+@SmallTest
+@RunWith(AndroidJUnit4::class)
class HotseatReorderUnitTest {
private val applicationContext: Context =
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index 0ff7c20..8a9711d 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -215,9 +215,11 @@
testCase.spanX, testCase.spanY, testCase.minSpanX, testCase.minSpanY,
isMultiCellLayout);
assertEquals("should be a valid solution", solution.isSolution, testCase.isValidSolution);
+ Log.d(TAG, "test case:" + testCase);
if (testCase.isValidSolution) {
CellLayoutBoard finishBoard = boardFromSolution(solution,
testCase.startBoard.getWidth(), testCase.startBoard.getHeight());
+ Log.d(TAG, "finishBoard case:" + finishBoard);
assertTrue("End result and test case result board doesn't match ",
finishBoard.compareTo(testCase.endBoard) == 0);
}
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTestCase.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTestCase.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTestCase.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTestCase.java
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
index 0bec1b2..a9355ec 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
@@ -141,11 +141,14 @@
ReorderPreviewAnimation.MODE_PREVIEW,
AnimationValues(dx = 0, dy = 0, scale = 100)
)
- testAnimationAtGivenProgress(
- PREVIEW_DURATION * 99,
- ReorderPreviewAnimation.MODE_PREVIEW,
- AnimationValues(dx = 5, dy = -10, scale = 96)
- )
+ // (b/339313407) Temporarily disable this test as the behavior is
+ // inconsistent between Soong & Gradle builds.
+ //
+ // testAnimationAtGivenProgress(
+ // PREVIEW_DURATION * 99,
+ // ReorderPreviewAnimation.MODE_PREVIEW,
+ // AnimationValues(dx = 5, dy = -10, scale = 96)
+ // )
testAnimationAtGivenProgress(
PREVIEW_DURATION * 98,
ReorderPreviewAnimation.MODE_PREVIEW,
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderTestCase.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderTestCase.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/ReorderTestCase.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderTestCase.java
diff --git a/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
diff --git a/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
similarity index 96%
rename from tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
index 62f2259..e5ad888 100644
--- a/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
@@ -199,6 +199,19 @@
return 'z';
}
+ /**
+ * Check if the given area is empty.
+ */
+ public boolean isEmpty(int x, int y, int spanX, int spanY) {
+ for (int xi = x; xi < x + spanX; xi++) {
+ for (int yi = y; yi < y + spanY; yi++) {
+ if (mWidget[xi][yi] == CellType.IGNORE) continue;
+ if (mWidget[xi][yi] != CellType.EMPTY) return false;
+ }
+ }
+ return true;
+ }
+
public void addWidget(int x, int y, int spanX, int spanY, char type) {
Rect rect = new Rect(x, y + spanY - 1, x + spanX - 1, y);
removeOverlappingItems(rect);
diff --git a/tests/src/com/android/launcher3/celllayout/board/CellType.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/board/CellType.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java
diff --git a/tests/src/com/android/launcher3/celllayout/board/FolderPoint.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/board/FolderPoint.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java
diff --git a/tests/src/com/android/launcher3/celllayout/board/IconPoint.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/board/IconPoint.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java
diff --git a/tests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt
diff --git a/tests/src/com/android/launcher3/celllayout/board/PermutedBoardComparator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/PermutedBoardComparator.kt
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/board/PermutedBoardComparator.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/board/PermutedBoardComparator.kt
diff --git a/tests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java
similarity index 94%
rename from tests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java
index 06a7db2..8a427dd 100644
--- a/tests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java
@@ -22,7 +22,6 @@
import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Rect;
import android.os.Process;
@@ -32,11 +31,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
-import com.android.launcher3.celllayout.board.CellLayoutBoard;
-import com.android.launcher3.celllayout.board.CellType;
-import com.android.launcher3.celllayout.board.FolderPoint;
-import com.android.launcher3.celllayout.board.IconPoint;
-import com.android.launcher3.celllayout.board.WidgetRect;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -56,12 +50,10 @@
private UserHandle mMyUser;
private Context mContext;
- private ContentResolver mResolver;
public TestWorkspaceBuilder(Context context) {
mMyUser = Process.myUserHandle();
mContext = context;
- mResolver = mContext.getContentResolver();
}
/**
diff --git a/tests/src/com/android/launcher3/celllayout/board/WidgetRect.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/board/WidgetRect.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
similarity index 81%
rename from tests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
index c5dbce4..ff46987 100644
--- a/tests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
@@ -21,6 +21,13 @@
/** Generates a random CellLayoutBoard. */
open class RandomBoardGenerator(generator: Random) : DeterministicRandomGenerator(generator) {
+
+ companion object {
+ // This is the max number of widgets because we encode the widgets as letters A-Z and we
+ // already have some of those letter used by other things so 22 is a safe number
+ val MAX_NUMBER_OF_WIDGETS = 22
+ }
+
/**
* @param remainingEmptySpaces the maximum number of spaces we will fill with icons and widgets
* meaning that if the number is 100 we will try to fill the board with at most 100 spaces
@@ -33,9 +40,9 @@
}
protected fun fillBoard(
- board: CellLayoutBoard,
- area: Rect,
- remainingEmptySpacesArg: Int
+ board: CellLayoutBoard,
+ area: Rect,
+ remainingEmptySpacesArg: Int
): CellLayoutBoard {
var remainingEmptySpaces = remainingEmptySpacesArg
if (area.height() * area.width() <= 0) return board
@@ -45,11 +52,18 @@
val y = area.top + getRandom(0, area.height() - height)
if (remainingEmptySpaces > 0) {
remainingEmptySpaces -= width * height
- } else if (board.widgets.size <= 22 && width * height > 1) {
+ }
+
+ if (board.widgets.size <= MAX_NUMBER_OF_WIDGETS && width * height > 1) {
board.addWidget(x, y, width, height)
} else {
board.addIcon(x, y)
}
+
+ if (remainingEmptySpaces < 0) {
+ // optimization, no need to keep going
+ return board
+ }
fillBoard(board, Rect(area.left, area.top, area.right, y), remainingEmptySpaces)
fillBoard(board, Rect(area.left, y, x, area.bottom), remainingEmptySpaces)
fillBoard(board, Rect(x, y + height, area.right, area.bottom), remainingEmptySpaces)
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt
diff --git a/tests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
rename to tests/multivalentTests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
index fbbfb2a..58dce0b 100644
--- a/tests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
@@ -38,7 +38,8 @@
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.util.LauncherMultivalentJUnit;
import org.junit.Before;
import org.junit.Test;
@@ -51,8 +52,8 @@
* Tests for FastBitmapDrawable.
*/
@SmallTest
+@RunWith(LauncherMultivalentJUnit.class)
@UiThreadTest
-@RunWith(AndroidJUnit4.class)
public class FastBitmapDrawableTest {
private static final float EPSILON = 0.00001f;
diff --git a/tests/src/com/android/launcher3/icons/IconCacheTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/icons/IconCacheTest.java
rename to tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt
new file mode 100644
index 0000000..d611ae8
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.icons.UserBadgeDrawable.SHADOW_COLOR
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/** Test for [UserBadgeDrawable] */
+@RunWith(AndroidJUnit4::class)
+class UserBadgeDrawableTest {
+ private val context = InstrumentationRegistry.getInstrumentation().targetContext
+ private val canvas = mock<Canvas>()
+ private val systemUnderTest =
+ UserBadgeDrawable(context, R.drawable.ic_work_app_badge, R.color.badge_tint_work, false)
+
+ @Test
+ fun draw_opaque() {
+ val colorList = mutableListOf<Int>()
+ whenever(
+ canvas.drawCircle(
+ any(),
+ any(),
+ any(),
+ any()
+ )
+ ).then { colorList.add(it.getArgument<Paint>(3).color) }
+
+ systemUnderTest.alpha = 255
+ systemUnderTest.draw(canvas)
+
+ assertThat(colorList).containsExactly(SHADOW_COLOR, Color.WHITE)
+ }
+
+ @Test
+ fun draw_transparent() {
+ val colorList = mutableListOf<Int>()
+ whenever(
+ canvas.drawCircle(
+ any(),
+ any(),
+ any(),
+ any()
+ )
+ ).then { colorList.add(it.getArgument<Paint>(3).color) }
+
+ systemUnderTest.alpha = 0
+ systemUnderTest.draw(canvas)
+
+ assertThat(colorList).hasSize(2)
+ assertThat(Color.valueOf(colorList[0]).alpha()).isEqualTo(0)
+ assertThat(Color.valueOf(colorList[1]).alpha()).isEqualTo(0)
+ }
+}
diff --git a/tests/src/com/android/launcher3/logging/FileLogTest.java b/tests/multivalentTests/src/com/android/launcher3/logging/FileLogTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/logging/FileLogTest.java
rename to tests/multivalentTests/src/com/android/launcher3/logging/FileLogTest.java
diff --git a/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt b/tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
similarity index 98%
rename from tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
index 130dfad..713d4d5 100644
--- a/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
@@ -2,8 +2,8 @@
import androidx.core.util.isEmpty
import androidx.test.annotation.UiThreadTest
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.launcher3.util.LauncherMultivalentJUnit
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -11,7 +11,7 @@
/** Unit test for [ColdRebootStartupLatencyLogger]. */
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(LauncherMultivalentJUnit::class)
class StartupLatencyLoggerTest {
private val underTest = ColdRebootStartupLatencyLogger()
diff --git a/tests/src/com/android/launcher3/model/DatabaseHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/model/DatabaseHelperTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
similarity index 90%
rename from tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
index 04735f2..761f06d 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
@@ -28,7 +28,6 @@
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE
import com.android.launcher3.LauncherSettings.Favorites.*
-import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.model.GridSizeMigrationUtil.DbReader
import com.android.launcher3.pm.UserCache
import com.android.launcher3.provider.LauncherDbUtils
@@ -98,10 +97,7 @@
modelHelper.destroy()
}
- /**
- * Old migration logic, should be modified once [FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC] is not
- * needed anymore
- */
+ /** Old migration logic, should be modified once is not needed anymore */
@Test
@Throws(Exception::class)
fun testMigration() {
@@ -208,10 +204,7 @@
assertThat(locMap[testPackage9]).isEqualTo(Point(0, 2))
}
- /**
- * Old migration logic, should be modified once [FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC] is not
- * needed anymore
- */
+ /** Old migration logic, should be modified once is not needed anymore */
@Test
@Throws(Exception::class)
fun testMigrationBackAndForth() {
@@ -606,68 +599,6 @@
}
/**
- * Migrating from a smaller grid to a large one should keep the pages if the column difference
- * is less than 2
- */
- @Test
- @Throws(Exception::class)
- fun migrateFromSmallerGridSmallDifference() {
- enableNewMigrationLogic("4,4")
-
- // Setup src grid
- addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage1, 5, TMP_TABLE)
- addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 3, testPackage2, 6, TMP_TABLE)
- addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 3, 1, testPackage3, 7, TMP_TABLE)
- addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 3, 2, testPackage4, 8, TMP_TABLE)
- addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_DESKTOP, 3, 3, testPackage5, 9, TMP_TABLE)
-
- idp.numDatabaseHotseatIcons = 4
- idp.numColumns = 6
- idp.numRows = 5
-
- val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
- val destReader = DbReader(db, TABLE_NAME, context, validPackages)
- GridSizeMigrationUtil.migrate(
- dbHelper,
- srcReader,
- destReader,
- idp.numDatabaseHotseatIcons,
- Point(idp.numColumns, idp.numRows),
- DeviceGridState(context),
- DeviceGridState(idp)
- )
-
- // Get workspace items
- val c =
- db.query(
- TABLE_NAME,
- arrayOf(INTENT, SCREEN),
- "container=$CONTAINER_DESKTOP",
- null,
- null,
- null,
- null
- )
- ?: throw IllegalStateException()
- val intentIndex = c.getColumnIndex(INTENT)
- val screenIndex = c.getColumnIndex(SCREEN)
-
- // Get in which screen the icon is
- val locMap = HashMap<String?, Int>()
- while (c.moveToNext()) {
- locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
- c.getInt(screenIndex)
- }
- c.close()
- assertThat(locMap.size).isEqualTo(5)
- assertThat(locMap[testPackage1]).isEqualTo(0)
- assertThat(locMap[testPackage2]).isEqualTo(0)
- assertThat(locMap[testPackage3]).isEqualTo(1)
- assertThat(locMap[testPackage4]).isEqualTo(1)
- assertThat(locMap[testPackage5]).isEqualTo(2)
- }
-
- /**
* Migrating from a smaller grid to a large one should reflow the pages if the column difference
* is more than 2
*/
diff --git a/tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt
diff --git a/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java b/tests/multivalentTests/src/com/android/launcher3/popup/PopupPopulatorTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
rename to tests/multivalentTests/src/com/android/launcher3/popup/PopupPopulatorTest.java
diff --git a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
similarity index 93%
rename from tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
index c99da96..3dca35e 100644
--- a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
@@ -22,7 +22,6 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
-import com.android.launcher3.tests.R as TestR
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -45,7 +44,7 @@
fun parseValidFile() {
val allAppsSpecs =
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.valid_all_apps_file),
+ TestResourceHelper(context, "valid_all_apps_file".xmlToId()),
ResponsiveSpecType.AllApps
)
@@ -114,7 +113,7 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_missingTag_throwsError() {
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_1),
+ TestResourceHelper(context, "invalid_all_apps_file_case_1".xmlToId()),
ResponsiveSpecType.AllApps
)
}
@@ -122,7 +121,7 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_2),
+ TestResourceHelper(context, "invalid_all_apps_file_case_2".xmlToId()),
ResponsiveSpecType.AllApps
)
}
@@ -130,7 +129,7 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_valueBiggerThan1_throwsError() {
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_3),
+ TestResourceHelper(context, "invalid_all_apps_file_case_3".xmlToId()),
ResponsiveSpecType.AllApps
)
}
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
similarity index 94%
rename from tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
index 1cc5ed2..8346492 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
@@ -23,7 +23,6 @@
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R as TestR
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -52,7 +51,7 @@
val workspaceSpecs =
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.valid_workspace_file),
+ TestResourceHelper(context, "valid_workspace_file".xmlToId()),
ResponsiveSpecType.Workspace
)
val widthSpec =
@@ -62,7 +61,7 @@
val allAppsSpecs =
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.valid_all_apps_file),
+ TestResourceHelper(context, "valid_all_apps_file".xmlToId()),
ResponsiveSpecType.AllApps
)
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
index c4e2d2a..46d6cb9 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
@@ -23,7 +23,6 @@
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -47,12 +46,12 @@
val columns = 6
// Loading workspace specs
- val resourceHelperWorkspace = TestResourceHelper(context, R.xml.valid_workspace_file)
+ val resourceHelperWorkspace = TestResourceHelper(context, "valid_workspace_file".xmlToId())
val workspaceSpecs =
ResponsiveSpecsProvider.create(resourceHelperWorkspace, ResponsiveSpecType.Workspace)
// Loading folders specs
- val resourceHelperFolder = TestResourceHelper(context, R.xml.valid_folders_specs)
+ val resourceHelperFolder = TestResourceHelper(context, "valid_folders_specs".xmlToId())
val folderSpecs =
ResponsiveSpecsProvider.create(resourceHelperFolder, ResponsiveSpecType.Folder)
val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
@@ -123,12 +122,12 @@
val rows = 5
// Loading workspace specs
- val resourceHelperWorkspace = TestResourceHelper(context, R.xml.valid_workspace_file)
+ val resourceHelperWorkspace = TestResourceHelper(context, "valid_workspace_file".xmlToId())
val workspaceSpecs =
ResponsiveSpecsProvider.create(resourceHelperWorkspace, ResponsiveSpecType.Workspace)
// Loading folders specs
- val resourceHelperFolder = TestResourceHelper(context, R.xml.valid_folders_specs)
+ val resourceHelperFolder = TestResourceHelper(context, "valid_folders_specs".xmlToId())
val folderSpecs =
ResponsiveSpecsProvider.create(resourceHelperFolder, ResponsiveSpecType.Folder)
val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
index 1a564ac..5ab44f3 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
@@ -22,7 +22,6 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R as TestR
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -47,7 +46,7 @@
val availableHeight = deviceSpec.naturalSize.second
val hotseatSpecsProvider =
- HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+ HotseatSpecsProvider.create(TestResourceHelper(context, "valid_hotseat_file".xmlToId()))
val heightSpec =
hotseatSpecsProvider.getCalculatedSpec(
aspectRatio,
@@ -73,7 +72,7 @@
val availableHeight = deviceSpec.naturalSize.second
val hotseatSpecsProvider =
- HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+ HotseatSpecsProvider.create(TestResourceHelper(context, "valid_hotseat_file".xmlToId()))
val heightSpec =
hotseatSpecsProvider.getCalculatedSpec(
aspectRatio,
@@ -99,7 +98,7 @@
val hotseatSpecsProvider =
HotseatSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.valid_hotseat_land_file)
+ TestResourceHelper(context, "valid_hotseat_land_file".xmlToId())
)
val widthSpec =
hotseatSpecsProvider.getCalculatedSpec(aspectRatio, DimensionType.WIDTH, availableWidth)
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
index 0c5d347..dea98b6 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
@@ -23,7 +23,6 @@
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R as TestR
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -53,7 +52,7 @@
val workspaceSpecs =
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.valid_workspace_file),
+ TestResourceHelper(context, "valid_workspace_file".xmlToId()),
ResponsiveSpecType.Workspace
)
val widthSpec =
@@ -96,7 +95,7 @@
val workspaceSpecs =
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.valid_workspace_file),
+ TestResourceHelper(context, "valid_workspace_file".xmlToId()),
ResponsiveSpecType.Workspace
)
val widthSpec =
@@ -138,7 +137,7 @@
val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 640
val workspaceSpecs =
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.valid_workspace_unsorted_file),
+ TestResourceHelper(context, "valid_workspace_unsorted_file".xmlToId()),
ResponsiveSpecType.Workspace
)
val widthSpec =
diff --git a/tests/src/com/android/launcher3/responsive/FolderSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/FolderSpecTest.kt
similarity index 91%
rename from tests/src/com/android/launcher3/responsive/FolderSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/FolderSpecTest.kt
index 5cfa49f..d2b264b 100644
--- a/tests/src/com/android/launcher3/responsive/FolderSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/FolderSpecTest.kt
@@ -23,7 +23,6 @@
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -44,7 +43,7 @@
@Test
fun parseValidFile() {
- val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+ val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
@@ -92,25 +91,25 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_missingTag_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_1)
+ val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_1".xmlToId())
ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_2)
+ val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_2".xmlToId())
ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_valueBiggerThan1_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_3)
+ val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_3".xmlToId())
ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_missingSpecs_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_4)
+ val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_4".xmlToId())
ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
}
@@ -132,7 +131,7 @@
val calculatedWorkspaceSpec =
CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_5)
+ val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_5".xmlToId())
val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
folderSpecs.getCalculatedSpec(
aspectRatio,
@@ -161,7 +160,7 @@
val calculatedWorkspaceSpec =
CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_5)
+ val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_5".xmlToId())
val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
folderSpecs.getCalculatedSpec(
aspectRatio,
@@ -190,7 +189,7 @@
val calculatedWorkspaceSpec =
CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
- val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+ val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
val calculatedWidthSpec =
folderSpecs.getCalculatedSpec(
@@ -227,7 +226,7 @@
val calculatedWorkspaceSpec =
CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
- val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+ val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
folderSpecs.getCalculatedSpec(
aspectRatio,
@@ -256,7 +255,7 @@
val calculatedWorkspaceSpec =
CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
- val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+ val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
val calculatedHeightSpec =
folderSpecs.getCalculatedSpec(
@@ -293,7 +292,7 @@
val calculatedWorkspaceSpec =
CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
- val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+ val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
folderSpecs.getCalculatedSpec(
aspectRatio,
diff --git a/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
similarity index 93%
rename from tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
index 78cb1ac..58324e1 100644
--- a/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
@@ -22,7 +22,6 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
-import com.android.launcher3.tests.R as TestR
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -44,7 +43,7 @@
@Test
fun parseValidFile() {
val hotseatSpecsProvider =
- HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+ HotseatSpecsProvider.create(TestResourceHelper(context, "valid_hotseat_file".xmlToId()))
val specs = hotseatSpecsProvider.getSpecsByAspectRatio(aspectRatio)
val expectedHeightSpecs =
@@ -76,7 +75,7 @@
fun parseValidLandscapeFile() {
val hotseatSpecsProvider =
HotseatSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.valid_hotseat_land_file)
+ TestResourceHelper(context, "valid_hotseat_land_file".xmlToId())
)
val specs = hotseatSpecsProvider.getSpecsByAspectRatio(aspectRatio)
assertThat(specs.heightSpecs.size).isEqualTo(0)
@@ -107,14 +106,14 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_spaceIsNotFixedSize_throwsError() {
HotseatSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_hotseat_file_case_1)
+ TestResourceHelper(context, "invalid_hotseat_file_case_1".xmlToId())
)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_invalidFixedSize_throwsError() {
HotseatSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_hotseat_file_case_2)
+ TestResourceHelper(context, "invalid_hotseat_file_case_2".xmlToId())
)
}
}
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
similarity index 92%
rename from tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
index 50cd358..11161bd 100644
--- a/tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
@@ -22,7 +22,6 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
-import com.android.launcher3.tests.R as TestR
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -43,7 +42,7 @@
@Test
fun parseValidFile() {
- val testResourceHelper = TestResourceHelper(context, TestR.xml.valid_cell_specs_file)
+ val testResourceHelper = TestResourceHelper(context, "valid_cell_specs_file".xmlToId())
val provider = ResponsiveCellSpecsProvider.create(testResourceHelper)
// Validate Portrait
@@ -98,21 +97,21 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_IsNotFixedSizeOrMatchWorkspace_throwsError() {
ResponsiveCellSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_cell_specs_1)
+ TestResourceHelper(context, "invalid_cell_specs_1".xmlToId())
)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_dimensionTypeIsNotHeight_throwsError() {
ResponsiveCellSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_cell_specs_2)
+ TestResourceHelper(context, "invalid_cell_specs_2".xmlToId())
)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_invalidFixedSize_throwsError() {
ResponsiveCellSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_cell_specs_3)
+ TestResourceHelper(context, "invalid_cell_specs_3".xmlToId())
)
}
}
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
similarity index 93%
rename from tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
index 54a1dc5..c74f7a8 100644
--- a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
@@ -23,7 +23,6 @@
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -44,7 +43,7 @@
@Test
fun parseValidFile() {
- val resourceHelper = TestResourceHelper(context, R.xml.valid_responsive_spec_unsorted)
+ val resourceHelper = TestResourceHelper(context, "valid_responsive_spec_unsorted".xmlToId())
val provider = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
// Validate Portrait
@@ -111,44 +110,44 @@
@Test(expected = IllegalStateException::class)
fun parseValidFile_invalidAspectRatio_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.valid_responsive_spec_unsorted)
+ val resourceHelper = TestResourceHelper(context, "valid_responsive_spec_unsorted".xmlToId())
val provider = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
provider.getSpecsByAspectRatio(0f)
}
@Test(expected = InvalidResponsiveGridSpec::class)
fun parseInvalidFile_missingGroups_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_1)
+ val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_1".xmlToId())
ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
}
@Test(expected = InvalidResponsiveGridSpec::class)
fun parseInvalidFile_partialGroups_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_2)
+ val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_2".xmlToId())
ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_invalidAspectRatio_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_3)
+ val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_3".xmlToId())
ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_invalidRemainderSpace_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_4)
+ val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_4".xmlToId())
ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_invalidAvailableSpace_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_5)
+ val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_5".xmlToId())
ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_invalidFixedSize_throwsError() {
- val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_6)
+ val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_6".xmlToId())
ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
}
diff --git a/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/SizeSpecTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/SizeSpecTest.kt
diff --git a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
similarity index 94%
rename from tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
index 17b0ee4..bc133ba 100644
--- a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
@@ -22,7 +22,6 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
-import com.android.launcher3.tests.R as TestR
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -45,7 +44,7 @@
fun parseValidFile() {
val workspaceSpecs =
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.valid_workspace_file),
+ TestResourceHelper(context, "valid_workspace_file".xmlToId()),
ResponsiveSpecType.Workspace
)
@@ -169,7 +168,7 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_missingTag_throwsError() {
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_1),
+ TestResourceHelper(context, "invalid_workspace_file_case_1".xmlToId()),
ResponsiveSpecType.Workspace
)
}
@@ -177,7 +176,7 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_2),
+ TestResourceHelper(context, "invalid_workspace_file_case_2".xmlToId()),
ResponsiveSpecType.Workspace
)
}
@@ -185,7 +184,7 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_valueBiggerThan1_throwsError() {
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_3),
+ TestResourceHelper(context, "invalid_workspace_file_case_3".xmlToId()),
ResponsiveSpecType.Workspace
)
}
@@ -193,7 +192,7 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_matchWorkspace_true_throwsError() {
ResponsiveSpecsProvider.create(
- TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_4),
+ TestResourceHelper(context, "invalid_workspace_file_case_4".xmlToId()),
ResponsiveSpecType.Workspace
)
}
diff --git a/tests/src/com/android/launcher3/settings/SettingsActivityTest.java b/tests/multivalentTests/src/com/android/launcher3/settings/SettingsActivityTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/settings/SettingsActivityTest.java
rename to tests/multivalentTests/src/com/android/launcher3/settings/SettingsActivityTest.java
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java b/tests/multivalentTests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
similarity index 100%
rename from tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
rename to tests/multivalentTests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetHidden.java b/tests/multivalentTests/src/com/android/launcher3/testcomponent/AppWidgetHidden.java
similarity index 100%
rename from tests/src/com/android/launcher3/testcomponent/AppWidgetHidden.java
rename to tests/multivalentTests/src/com/android/launcher3/testcomponent/AppWidgetHidden.java
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java b/tests/multivalentTests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
similarity index 100%
rename from tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
rename to tests/multivalentTests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java b/tests/multivalentTests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
similarity index 100%
rename from tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
rename to tests/multivalentTests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
diff --git a/tests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java
rename to tests/multivalentTests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
rename to tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index 90ded10..b83349e 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -37,6 +37,9 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.ViewGroup;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
@@ -52,6 +55,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
/**
@@ -61,6 +65,8 @@
* two lines, and this is enough to ensure whether the string should be specifically wrapped onto
* the second line and to ensure truncation.
*/
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class BubbleTextViewTest {
@Rule public final SetFlagsRule mSetFlagsRule =
diff --git a/tests/src/com/android/launcher3/ui/TestViewHelpers.java b/tests/multivalentTests/src/com/android/launcher3/ui/TestViewHelpers.java
similarity index 100%
rename from tests/src/com/android/launcher3/ui/TestViewHelpers.java
rename to tests/multivalentTests/src/com/android/launcher3/ui/TestViewHelpers.java
diff --git a/tests/src/com/android/launcher3/util/CellContentDimensionsTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/CellContentDimensionsTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/util/CellContentDimensionsTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/CellContentDimensionsTest.kt
diff --git a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
similarity index 91%
rename from tests/src/com/android/launcher3/util/DisplayControllerTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
index 706ab27..41effa2 100644
--- a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -27,10 +27,10 @@
import android.view.Surface
import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
+import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING
@@ -45,6 +45,7 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
@@ -53,7 +54,7 @@
/** Unit tests for {@link DisplayController} */
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(LauncherMultivalentJUnit::class)
class DisplayControllerTest {
private val appContext: Context = ApplicationProvider.getApplicationContext()
@@ -93,6 +94,7 @@
whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
+ whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true)
// Mock WindowManagerProxy
val displayInfo = CachedDisplayInfo(Point(width, height), Surface.ROTATION_0)
@@ -123,6 +125,7 @@
whenever(displayManager.getDisplay(any())).thenReturn(display)
// Mock resources
+ doReturn(context).whenever(context).applicationContext
whenever(resources.configuration).thenReturn(configuration)
whenever(context.resources).thenReturn(resources)
@@ -168,4 +171,13 @@
verify(displayInfoChangeListener)
.onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
}
+
+ @Test
+ @UiThreadTest
+ fun testTaskbarPinningChangeInDesktopMode() {
+ whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
+ displayController.handleInfoChange(display)
+ verify(displayInfoChangeListener)
+ .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+ }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
deleted file mode 100644
index ba01b04..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.android.launcher3.util;
-
-
-import android.text.TextUtils;
-import android.util.Pair;
-import android.util.Xml;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.StringWriter;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Helper class to build xml for Launcher Layout
- */
-public class LauncherLayoutBuilder {
-
- // Object Tags
- private static final String TAG_WORKSPACE = "workspace";
- private static final String TAG_AUTO_INSTALL = "autoinstall";
- private static final String TAG_FOLDER = "folder";
- private static final String TAG_APPWIDGET = "appwidget";
- private static final String TAG_SHORTCUT = "shortcut";
- private static final String TAG_EXTRA = "extra";
-
- private static final String ATTR_CONTAINER = "container";
- private static final String ATTR_RANK = "rank";
-
- private static final String ATTR_PACKAGE_NAME = "packageName";
- private static final String ATTR_CLASS_NAME = "className";
- private static final String ATTR_TITLE = "title";
- private static final String ATTR_TITLE_TEXT = "titleText";
- private static final String ATTR_SCREEN = "screen";
- private static final String ATTR_SHORTCUT_ID = "shortcutId";
-
- // x and y can be specified as negative integers, in which case -1 represents the
- // last row / column, -2 represents the second last, and so on.
- private static final String ATTR_X = "x";
- private static final String ATTR_Y = "y";
- private static final String ATTR_SPAN_X = "spanX";
- private static final String ATTR_SPAN_Y = "spanY";
-
- private static final String ATTR_CHILDREN = "children";
-
-
- // Style attrs -- "Extra"
- private static final String ATTR_KEY = "key";
- private static final String ATTR_VALUE = "value";
-
- private static final String CONTAINER_DESKTOP = "desktop";
- private static final String CONTAINER_HOTSEAT = "hotseat";
-
- private final ArrayList<Pair<String, HashMap<String, Object>>> mNodes = new ArrayList<>();
-
- public Location atHotseat(int rank) {
- Location l = new Location();
- l.items.put(ATTR_CONTAINER, CONTAINER_HOTSEAT);
- l.items.put(ATTR_RANK, Integer.toString(rank));
- return l;
- }
-
- public Location atWorkspace(int x, int y, int screen) {
- Location l = new Location();
- l.items.put(ATTR_CONTAINER, CONTAINER_DESKTOP);
- l.items.put(ATTR_X, Integer.toString(x));
- l.items.put(ATTR_Y, Integer.toString(y));
- l.items.put(ATTR_SCREEN, Integer.toString(screen));
- return l;
- }
-
- public String build() throws IOException {
- StringWriter writer = new StringWriter();
- build(writer);
- return writer.toString();
- }
-
- public void build(Writer writer) throws IOException {
- XmlSerializer serializer = Xml.newSerializer();
- serializer.setOutput(writer);
-
- serializer.startDocument("UTF-8", true);
- serializer.startTag(null, TAG_WORKSPACE);
- writeNodes(serializer, mNodes);
- serializer.endTag(null, TAG_WORKSPACE);
- serializer.endDocument();
- serializer.flush();
- }
-
- private static void writeNodes(XmlSerializer serializer,
- ArrayList<Pair<String, HashMap<String, Object>>> nodes) throws IOException {
- for (Pair<String, HashMap<String, Object>> node : nodes) {
- ArrayList<Pair<String, HashMap<String, Object>>> children = null;
-
- serializer.startTag(null, node.first);
- for (Map.Entry<String, Object> attr : node.second.entrySet()) {
- if (ATTR_CHILDREN.equals(attr.getKey())) {
- children = (ArrayList<Pair<String, HashMap<String, Object>>>) attr.getValue();
- } else {
- serializer.attribute(null, attr.getKey(), (String) attr.getValue());
- }
- }
-
- if (children != null) {
- writeNodes(serializer, children);
- }
- serializer.endTag(null, node.first);
- }
- }
-
- public class Location {
-
- final HashMap<String, Object> items = new HashMap<>();
-
- public LauncherLayoutBuilder putApp(String packageName, String className) {
- items.put(ATTR_PACKAGE_NAME, packageName);
- items.put(ATTR_CLASS_NAME, TextUtils.isEmpty(className) ? packageName : className);
- mNodes.add(Pair.create(TAG_AUTO_INSTALL, items));
- return LauncherLayoutBuilder.this;
- }
-
- public LauncherLayoutBuilder putShortcut(String packageName, String shortcutId) {
- items.put(ATTR_PACKAGE_NAME, packageName);
- items.put(ATTR_SHORTCUT_ID, shortcutId);
- mNodes.add(Pair.create(TAG_SHORTCUT, items));
- return LauncherLayoutBuilder.this;
- }
-
- public LauncherLayoutBuilder putWidget(String packageName, String className,
- int spanX, int spanY) {
- items.put(ATTR_PACKAGE_NAME, packageName);
- items.put(ATTR_CLASS_NAME, className);
- items.put(ATTR_SPAN_X, Integer.toString(spanX));
- items.put(ATTR_SPAN_Y, Integer.toString(spanY));
- mNodes.add(Pair.create(TAG_APPWIDGET, items));
- return LauncherLayoutBuilder.this;
- }
-
- public FolderBuilder putFolder(int titleResId) {
- items.put(ATTR_TITLE, Integer.toString(titleResId));
- return putFolder();
- }
-
- public FolderBuilder putFolder(String title) {
- items.put(ATTR_TITLE_TEXT, title);
- return putFolder();
- }
-
- private FolderBuilder putFolder() {
- FolderBuilder folderBuilder = new FolderBuilder();
- items.put(ATTR_CHILDREN, folderBuilder.mChildren);
- mNodes.add(Pair.create(TAG_FOLDER, items));
- return folderBuilder;
- }
- }
-
- public class FolderBuilder {
-
- final ArrayList<Pair<String, HashMap<String, Object>>> mChildren = new ArrayList<>();
-
- public FolderBuilder addApp(String packageName, String className) {
- HashMap<String, Object> items = new HashMap<>();
- items.put(ATTR_PACKAGE_NAME, packageName);
- items.put(ATTR_CLASS_NAME, TextUtils.isEmpty(className) ? packageName : className);
- mChildren.add(Pair.create(TAG_AUTO_INSTALL, items));
- return this;
- }
-
- public FolderBuilder addShortcut(String packageName, String shortcutId) {
- HashMap<String, Object> items = new HashMap<>();
- items.put(ATTR_PACKAGE_NAME, packageName);
- items.put(ATTR_SHORTCUT_ID, shortcutId);
- mChildren.add(Pair.create(TAG_SHORTCUT, items));
- return this;
- }
-
- public LauncherLayoutBuilder build() {
- return LauncherLayoutBuilder.this;
- }
- }
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index e806d1d..f18c02b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -44,26 +44,16 @@
import android.test.mock.MockContentResolver;
import android.util.ArrayMap;
-import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.ItemInstallQueue;
-import com.android.launcher3.pm.InstallSessionHelper;
-import com.android.launcher3.pm.UserCache;
import com.android.launcher3.testing.TestInformationProvider;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
-import com.android.launcher3.util.window.WindowManagerProxy;
-import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -73,7 +63,6 @@
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
/**
* Utility class to help manage Launcher Model and related objects for test.
@@ -122,17 +111,9 @@
public synchronized BgDataModel getBgDataModel() {
if (mDataModel == null) {
- getModel().enqueueModelUpdateTask(new ModelUpdateTask() {
- @Override
- public void init(@NonNull LauncherAppState app, @NonNull LauncherModel model,
- @NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList,
- @NonNull Executor uiExecutor) {
- mDataModel = dataModel;
- }
-
- @Override
- public void run() { }
- });
+ getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
+ mDataModel = dataModel);
+ runOnExecutorSync(Executors.MODEL_EXECUTOR, () -> { });
}
return mDataModel;
}
@@ -234,13 +215,7 @@
private final File mDbDir;
public SandboxModelContext() {
- super(ApplicationProvider.getApplicationContext(),
- UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
- LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
- DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
- SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE,
- LockedUserState.INSTANCE, WallpaperColorHints.INSTANCE,
- ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
+ super(ApplicationProvider.getApplicationContext());
// System settings cache content provider. Ensure that they are statically initialized
Settings.Secure.getString(
@@ -255,18 +230,13 @@
}
@Override
- protected <T> T createObject(MainThreadInitializedObject<T> object) {
+ public <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
if (object == LauncherAppState.INSTANCE) {
return (T) new LauncherAppState(this, null /* iconCacheFileName */);
}
return super.createObject(object);
}
- public SandboxModelContext allow(MainThreadInitializedObject object) {
- mAllowedObjects.add(object);
- return this;
- }
-
@Override
public File getDatabasePath(String name) {
if (!mDbDir.exists()) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherMultivalentJUnit.kt b/tests/multivalentTests/src/com/android/launcher3/util/LauncherMultivalentJUnit.kt
new file mode 100644
index 0000000..e8560af
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherMultivalentJUnit.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.collect.ImmutableList
+import java.util.Locale
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.CLASS
+import org.junit.runner.Runner
+import org.junit.runners.Suite
+
+/**
+ * A custom runner for multivalent tests with launcher specific features
+ * 1) Adds support for @UiThread annotations in deviceless tests
+ * 2) Allows emulating multiple devices when running in deviceless mode
+ */
+class LauncherMultivalentJUnit(klass: Class<*>?) : Suite(klass, ImmutableList.of()) {
+
+ val runners: List<Runner> =
+ (testClass.getAnnotation(EmulatedDevices::class.java)?.value ?: emptyArray()).let { devices
+ ->
+ if (!isRunningInRobolectric) {
+ return@let null
+ }
+ try {
+ (testClass.javaClass.classLoader.loadClass(ROBOLECTRIC_RUNNER) as Class<Runner>)
+ .getConstructor(Class::class.java, String::class.java)
+ .let { ctor ->
+ if (devices.isEmpty()) listOf(ctor.newInstance(testClass.javaClass, null))
+ else devices.map { ctor.newInstance(testClass.javaClass, it) }
+ }
+ } catch (e: Exception) {
+ null
+ }
+ }
+ ?: listOf(AndroidJUnit4(testClass.javaClass))
+
+ override fun getChildren() = runners
+
+ /**
+ * Annotation to be added to a test so run it on a list of emulated devices for deviceless test
+ */
+ @Retention(RUNTIME) @Target(CLASS) annotation class EmulatedDevices(val value: Array<String>)
+
+ companion object {
+ private const val ROBOLECTRIC_RUNNER = "com.android.launcher3.util.RobolectricDeviceRunner"
+
+ val isRunningInRobolectric: Boolean
+ get() =
+ if (
+ System.getProperty("java.runtime.name")
+ .lowercase(Locale.getDefault())
+ .contains("android")
+ ) {
+ false
+ } else {
+ try {
+ // Check if robolectric runner exists
+ Class.forName("org.robolectric.RobolectricTestRunner") != null
+ } catch (e: ClassNotFoundException) {
+ false
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/util/LockedUserStateTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
index 2c4a54f..2711d7a 100644
--- a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -28,7 +28,7 @@
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
/** Unit tests for {@link LockedUserState} */
@@ -58,7 +58,8 @@
val action: Runnable = mock()
val state = LockedUserState(context)
state.runOnUserUnlocked(action)
- verifyZeroInteractions(action)
+ // b/343530737
+ verifyNoMoreInteractions(action)
state.mUserUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
verify(action).run()
}
diff --git a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
similarity index 100%
rename from tests/src/com/android/launcher3/util/ModelTestExtensions.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
diff --git a/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java b/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java
similarity index 93%
rename from tests/src/com/android/launcher3/util/PackageManagerHelperTest.java
rename to tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java
index d1da5f4..b5e797e 100644
--- a/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java
@@ -34,6 +34,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Rule;
@@ -63,6 +64,8 @@
mContext = mock(Context.class);
mLauncherApps = mock(LauncherApps.class);
when(mContext.getSystemService(eq(LauncherApps.class))).thenReturn(mLauncherApps);
+ when(mContext.getResources()).thenReturn(
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getResources());
mPackageManagerHelper = new PackageManagerHelper(mContext);
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/multivalentTests/src/com/android/launcher3/util/TestResourceHelper.kt
new file mode 100644
index 0000000..d95c6f8
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestResourceHelper.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.R
+import kotlin.IntArray
+
+class TestResourceHelper(private val context: Context, specsFileId: Int) :
+ ResourceHelper(context, specsFileId) {
+
+ val responsiveStyleables = listOf(
+ R.styleable.SizeSpec,
+ R.styleable.WorkspaceSpec,
+ R.styleable.FolderSpec,
+ R.styleable.AllAppsSpec,
+ R.styleable.ResponsiveSpecGroup
+ )
+
+ override fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
+ val clone =
+ if (responsiveStyleables.any { styleId.contentEquals(it) }) {
+ convertStyleId(styleId)
+ } else {
+ styleId.clone()
+ }
+
+ return context.obtainStyledAttributes(attrs, clone)
+ }
+
+ private fun convertStyleId(styleableArr: IntArray): IntArray {
+ val targetContextRes = getInstrumentation().targetContext.resources
+ val context = getInstrumentation().context
+ return styleableArr
+ .map { attrId -> targetContextRes.getResourceName(attrId).split(":").last() }
+ .map { attrName ->
+ // Get required attr from context instead of targetContext
+ context.resources.getIdentifier(attrName, null, context.packageName)
+ }
+ .toIntArray()
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
index 95444ba..3646f0c 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
@@ -46,12 +46,8 @@
import androidx.test.uiautomator.UiDevice;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.FeatureFlags.BooleanFlag;
-import com.android.launcher3.config.FeatureFlags.IntFlag;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.Workspace;
-import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Assert;
@@ -66,8 +62,6 @@
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.function.Predicate;
-import java.util.function.ToIntFunction;
public class TestUtil {
private static final String TAG = "TestUtil";
@@ -138,49 +132,12 @@
*/
public static Point[] getCornersAndCenterPositions(LauncherInstrumentation launcher) {
final Point dimensions = launcher.getWorkspace().getIconGridDimensions();
- if (TestStabilityRule.isPresubmit()) {
- // Return only center in presubmit to fit under the presubmit SLO.
- return new Point[]{
- new Point(dimensions.x / 2, dimensions.y / 2)
- };
- } else {
- return new Point[]{
- new Point(0, 1),
- new Point(0, dimensions.y - 2),
- new Point(dimensions.x - 1, 1),
- new Point(dimensions.x - 1, dimensions.y - 2),
- new Point(dimensions.x / 2, dimensions.y / 2)
- };
- }
- }
-
- /**
- * Utility class to override a boolean flag during test. Note that the returned SafeCloseable
- * must be closed to restore the original state
- */
- public static SafeCloseable overrideFlag(BooleanFlag flag, boolean value) {
- Predicate<BooleanFlag> originalProxy = FeatureFlags.sBooleanReader;
- Predicate<BooleanFlag> testProxy = f -> f == flag ? value : originalProxy.test(f);
- FeatureFlags.sBooleanReader = testProxy;
- return () -> {
- if (FeatureFlags.sBooleanReader == testProxy) {
- FeatureFlags.sBooleanReader = originalProxy;
- }
- };
- }
-
- /**
- * Utility class to override a int flag during test. Note that the returned SafeCloseable
- * must be closed to restore the original state
- */
- public static SafeCloseable overrideFlag(IntFlag flag, int value) {
- ToIntFunction<IntFlag> originalProxy = FeatureFlags.sIntReader;
- ToIntFunction<IntFlag> testProxy = f -> f == flag ? value : originalProxy.applyAsInt(f);
- FeatureFlags.sIntReader = testProxy;
- return () -> {
- if (FeatureFlags.sIntReader == testProxy) {
- FeatureFlags.sIntReader = originalProxy;
- }
+ return new Point[]{
+ new Point(0, 1),
+ new Point(0, dimensions.y - 2),
+ new Point(dimensions.x - 1, 1),
+ new Point(dimensions.x - 1, dimensions.y - 2),
+ new Point(dimensions.x / 2, dimensions.y / 2)
};
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
index 027a31a..deb0ef3 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
@@ -15,15 +15,12 @@
*/
package com.android.launcher3.util;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.os.Bundle;
-import android.os.Process;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -87,10 +84,10 @@
* Creates a {@link AppWidgetProviderInfo} for the provided component name
*/
public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
- AppWidgetProviderInfo info = AppWidgetManager.getInstance(getApplicationContext())
- .getInstalledProvidersForPackage(
- getInstrumentation().getContext().getPackageName(), Process.myUserHandle())
- .get(0);
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.applicationInfo = new ApplicationInfo();
+ AppWidgetProviderInfo info = new AppWidgetProviderInfo();
+ info.providerInfo = activityInfo;
info.provider = cn;
return info;
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/rule/BackAndRestoreRule.kt b/tests/multivalentTests/src/com/android/launcher3/util/rule/BackAndRestoreRule.kt
new file mode 100644
index 0000000..da96939
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/rule/BackAndRestoreRule.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util.rule
+
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherPrefs
+import java.io.File
+import java.nio.file.Paths
+import kotlin.io.path.pathString
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Removes all launcher's DBs from the device and copies the dbs in
+ * assets/databases/BackupAndRestore to the device. It also set's the needed LauncherPrefs variables
+ * needed to kickstart a backup and restore.
+ */
+class BackAndRestoreRule : TestRule {
+
+ private val phoneContext = getInstrumentation().targetContext
+
+ private fun dbBackUp() = File(phoneContext.dataDir.path, "/databasesBackUp")
+
+ private fun dbDirectory() = File(phoneContext.dataDir.path, "/databases")
+
+ private fun isWorkspaceDatabase(rawFileName: String): Boolean {
+ val fileName = Paths.get(rawFileName).fileName.pathString
+ return fileName.startsWith("launcher") && fileName.endsWith(".db")
+ }
+
+ fun getDatabaseFiles() = dbDirectory().listFiles().filter { isWorkspaceDatabase(it.name) }
+
+ /**
+ * Setting RESTORE_DEVICE would trigger a restore next time the Launcher starts, and we remove
+ * the widgets and apps ids to prevent issues when loading the database.
+ */
+ private fun setRestoreConstants() {
+ LauncherPrefs.get(phoneContext)
+ .put(LauncherPrefs.RESTORE_DEVICE.to(InvariantDeviceProfile.TYPE_MULTI_DISPLAY))
+ LauncherPrefs.get(phoneContext)
+ .remove(LauncherPrefs.OLD_APP_WIDGET_IDS, LauncherPrefs.APP_WIDGET_IDS)
+ }
+
+ private fun uploadDatabase(dbName: String) {
+ val file = File(File(getInstrumentation().targetContext.dataDir, "/databases"), dbName)
+ file.writeBytes(
+ getInstrumentation()
+ .context
+ .assets
+ .open("databases/BackupAndRestore/$dbName")
+ .readBytes()
+ )
+ file.setWritable(true, false)
+ }
+
+ private fun uploadDbs() {
+ uploadDatabase("launcher.db")
+ uploadDatabase("launcher_4_by_4.db")
+ uploadDatabase("launcher_4_by_5.db")
+ uploadDatabase("launcher_3_by_3.db")
+ }
+
+ private fun savePreviousState() {
+ dbBackUp().deleteRecursively()
+ if (!dbDirectory().renameTo(dbBackUp())) {
+ throw Exception("Unable to move databases to backup directory")
+ }
+ dbDirectory().mkdir()
+ if (!dbDirectory().exists()) {
+ throw Exception("Databases directory doesn't exists")
+ }
+ }
+
+ private fun restorePreviousState() {
+ dbDirectory().deleteRecursively()
+ if (!dbBackUp().renameTo(dbDirectory())) {
+ throw Exception("Unable to restore backup directory to databases directory")
+ }
+ dbBackUp().delete()
+ }
+
+ fun before() {
+ savePreviousState()
+ setRestoreConstants()
+ uploadDbs()
+ }
+
+ fun after() {
+ restorePreviousState()
+ }
+
+ override fun apply(base: Statement?, description: Description?): Statement =
+ object : Statement() {
+ override fun evaluate() {
+ before()
+ try {
+ base?.evaluate()
+ } finally {
+ after()
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt b/tests/multivalentTests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt
similarity index 100%
rename from tests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt
diff --git a/tests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt
diff --git a/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
similarity index 80%
rename from tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
index 11855e6..b239aed 100644
--- a/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
@@ -12,17 +12,18 @@
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.widget.RemoteViews
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.Flags.FLAG_ENABLE_GENERATED_PREVIEWS
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.icons.IconCache
import com.android.launcher3.icons.IconProvider
import com.android.launcher3.model.WidgetItem
-import com.android.launcher3.tests.R
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.util.Executors
import com.google.common.truth.Truth.assertThat
@@ -40,7 +41,10 @@
"com.android.launcher3.tests",
"com.android.launcher3.testcomponent.AppWidgetNoConfig"
)
- private val generatedPreviewLayout = R.layout.test_layout_appwidget_blue
+ private val generatedPreviewLayout =
+ getInstrumentation().context.run {
+ resources.getIdentifier("test_layout_appwidget_blue", "layout", packageName)
+ }
private lateinit var context: Context
private lateinit var generatedPreview: RemoteViews
private lateinit var widgetCell: WidgetCell
@@ -53,7 +57,14 @@
context = getApplicationContext()
generatedPreview = RemoteViews(context.packageName, generatedPreviewLayout)
widgetCell =
- LayoutInflater.from(ActivityContextWrapper(context))
+ LayoutInflater.from(
+ ActivityContextWrapper(
+ ContextThemeWrapper(
+ context,
+ com.android.launcher3.R.style.WidgetContainerTheme
+ )
+ )
+ )
.inflate(com.android.launcher3.R.layout.widget_cell, null) as WidgetCell
appWidgetProviderInfo =
AppWidgetProviderInfo()
@@ -111,12 +122,25 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
+ fun widgetItem_hasGeneratedPreview_nullPreview() {
+ appWidgetProviderInfo.generatedPreviewCategories =
+ WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD
+ createWidgetItem()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue()
+ // loadGeneratedPreview returns null for KEYGUARD, so this should still be false.
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
+ }
+
+ @Test
@RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetItem_hasGeneratedPreview_flagDisabled() {
assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isFalse()
assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
}
+
@Test
@RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetItem_getGeneratedPreview() {
@@ -128,6 +152,7 @@
@RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetCell_showGeneratedPreview() {
widgetCell.applyFromCellItem(widgetItem)
+ DatabaseWidgetPreviewLoader.getLoaderExecutor().submit {}.get()
assertThat(widgetCell.appWidgetHostViewPreview).isNotNull()
assertThat(widgetCell.appWidgetHostViewPreview?.appWidgetInfo)
.isEqualTo(appWidgetProviderInfo)
@@ -137,6 +162,7 @@
@RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetCell_showGeneratedPreview_flagDisabled() {
widgetCell.applyFromCellItem(widgetItem)
+ DatabaseWidgetPreviewLoader.getLoaderExecutor().submit {}.get()
assertThat(widgetCell.appWidgetHostViewPreview).isNull()
}
}
diff --git a/tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS b/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS
new file mode 100644
index 0000000..775b0c7
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS
@@ -0,0 +1,18 @@
+set noparent
+
+# Bug component: 1481801
+# People who can approve changes for submission
+#
+
+# Widget Picker OWNERS
[email protected]
[email protected]
[email protected]
+
+# For Tests
[email protected]
+
+# Launcher OWNERS
[email protected]
[email protected]
+
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetImageViewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetImageViewTest.kt
new file mode 100644
index 0000000..6e751e0
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetImageViewTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker
+
+import android.content.Context
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.widget.WidgetImageView
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class WidgetImageViewTest {
+ private lateinit var context: Context
+ private lateinit var widgetImageView: WidgetImageView
+
+ @Mock private lateinit var testDrawable: Drawable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+ widgetImageView = spy(WidgetImageView(context))
+ }
+
+ @Test
+ fun getBitmapBounds_aspectRatioLargerThanView_scaledByWidth() {
+ // view - 100 x 100
+ whenever(widgetImageView.width).thenReturn(100)
+ whenever(widgetImageView.height).thenReturn(100)
+ // bitmap - 200 x 100
+ whenever(testDrawable.intrinsicWidth).thenReturn(200)
+ whenever(testDrawable.intrinsicHeight).thenReturn(100)
+
+ widgetImageView.drawable = testDrawable
+ val bitmapBounds = widgetImageView.bitmapBounds
+
+ // new scaled width of bitmap is = 100, and height is scaled to 1/2 = 50
+ assertThat(bitmapBounds).isEqualTo(Rect(0, 25, 100, 75))
+ }
+
+ @Test
+ fun getBitmapBounds_aspectRatioSmallerThanView_scaledByHeight() {
+ // view - 100 x 100
+ whenever(widgetImageView.width).thenReturn(100)
+ whenever(widgetImageView.height).thenReturn(100)
+ // bitmap - 100 x 200
+ whenever(testDrawable.intrinsicWidth).thenReturn(100)
+ whenever(testDrawable.intrinsicHeight).thenReturn(200)
+ widgetImageView.drawable = testDrawable
+
+ val bitmapBounds = widgetImageView.bitmapBounds
+
+ // new scaled height of bitmap is = 100, and width is scaled to 1/2 = 50
+ assertThat(bitmapBounds).isEqualTo(Rect(25, 0, 75, 100))
+ }
+
+ @Test
+ fun getBitmapBounds_noScale_returnsOriginalDrawableBounds() {
+ // view - 200 x 100
+ whenever(widgetImageView.width).thenReturn(200)
+ whenever(widgetImageView.height).thenReturn(100)
+ // bitmap - 200 x 100
+ whenever(testDrawable.intrinsicWidth).thenReturn(200)
+ whenever(testDrawable.intrinsicHeight).thenReturn(100)
+
+ widgetImageView.drawable = testDrawable
+ val bitmapBounds = widgetImageView.bitmapBounds
+
+ // no scaling
+ assertThat(bitmapBounds).isEqualTo(Rect(0, 0, 200, 100))
+ }
+}
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
similarity index 68%
rename from tests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
index c807771..3024d26 100644
--- a/tests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
@@ -23,25 +23,23 @@
import static android.content.pm.ApplicationInfo.CATEGORY_SOCIAL;
import static android.content.pm.ApplicationInfo.CATEGORY_UNDEFINED;
import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO;
+import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
-import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
+import android.content.pm.LauncherApps;
import android.os.Process;
import androidx.test.core.content.pm.ApplicationInfoBuilder;
@@ -53,6 +51,7 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.google.common.collect.ImmutableMap;
@@ -70,10 +69,25 @@
public class WidgetRecommendationCategoryProviderTest {
private static final String TEST_PACKAGE = "com.foo.test";
private static final String TEST_APP_NAME = "foo";
- public static final WidgetRecommendationCategory SOCIAL_AND_ENTERTAINMENT_CATEGORY =
+ private static final WidgetRecommendationCategory PRODUCTIVITY =
new WidgetRecommendationCategory(
- R.string.social_and_entertainment_widget_recommendation_category_label,
+ R.string.productivity_widget_recommendation_category_label,
+ /*order=*/0);
+ private static final WidgetRecommendationCategory NEWS =
+ new WidgetRecommendationCategory(
+ R.string.news_widget_recommendation_category_label, /*order=*/1);
+ private static final WidgetRecommendationCategory SUGGESTED_FOR_YOU =
+ new WidgetRecommendationCategory(
+ R.string.others_widget_recommendation_category_label, /*order=*/2);
+ private static final WidgetRecommendationCategory SOCIAL =
+ new WidgetRecommendationCategory(
+ R.string.social_widget_recommendation_category_label,
+ /*order=*/3);
+ private static final WidgetRecommendationCategory ENTERTAINMENT =
+ new WidgetRecommendationCategory(
+ R.string.entertainment_widget_recommendation_category_label,
/*order=*/4);
+
private final ApplicationInfo mTestAppInfo = ApplicationInfoBuilder.newBuilder().setPackageName(
TEST_PACKAGE).setName(TEST_APP_NAME).build();
private Context mContext;
@@ -82,7 +96,7 @@
private WidgetItem mTestWidgetItem;
@Mock
- private PackageManager mPackageManager;
+ private LauncherApps mLauncherApps;
private InvariantDeviceProfile mTestProfile;
@Before
@@ -90,10 +104,12 @@
MockitoAnnotations.initMocks(this);
mContext = new ContextWrapper(getInstrumentation().getTargetContext()) {
@Override
- public PackageManager getPackageManager() {
- return mPackageManager;
+ public Object getSystemService(String name) {
+ return LAUNCHER_APPS_SERVICE.equals(name) ? mLauncherApps : super.getSystemService(
+ name);
}
};
+ mTestAppInfo.flags = FLAG_INSTALLED;
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
@@ -103,25 +119,22 @@
@Test
public void getWidgetRecommendationCategory_returnsMappedCategory() throws Exception {
ImmutableMap<Integer, WidgetRecommendationCategory> testCategories = ImmutableMap.of(
- CATEGORY_PRODUCTIVITY, new WidgetRecommendationCategory(
- R.string.productivity_widget_recommendation_category_label,
- /*order=*/
- 0),
- CATEGORY_NEWS, new WidgetRecommendationCategory(
- R.string.news_widget_recommendation_category_label, /*order=*/1),
- CATEGORY_SOCIAL, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
- CATEGORY_AUDIO, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
- CATEGORY_IMAGE, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
- CATEGORY_VIDEO, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
- CATEGORY_UNDEFINED, new WidgetRecommendationCategory(
- R.string.others_widget_recommendation_category_label, /*order=*/5));
+ CATEGORY_PRODUCTIVITY, PRODUCTIVITY,
+ CATEGORY_NEWS, NEWS,
+ CATEGORY_SOCIAL, SOCIAL,
+ CATEGORY_AUDIO, ENTERTAINMENT,
+ CATEGORY_IMAGE, ENTERTAINMENT,
+ CATEGORY_VIDEO, ENTERTAINMENT,
+ CATEGORY_UNDEFINED, SUGGESTED_FOR_YOU);
for (Map.Entry<Integer, WidgetRecommendationCategory> testCategory :
testCategories.entrySet()) {
mTestAppInfo.category = testCategory.getKey();
- when(mPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
- mTestAppInfo);
+ when(mLauncherApps.getApplicationInfo(/*packageName=*/ eq(TEST_PACKAGE),
+ /*flags=*/ eq(0),
+ /*user=*/ eq(Process.myUserHandle())))
+ .thenReturn(mTestAppInfo);
WidgetRecommendationCategory category = Executors.MODEL_EXECUTOR.submit(() ->
new WidgetRecommendationCategoryProvider().getWidgetRecommendationCategory(
@@ -138,11 +151,8 @@
doAnswer(invocation -> widgetLabel).when(mIconCache).getTitleNoCache(any());
- AppWidgetProviderInfo providerInfo = AppWidgetManager.getInstance(getApplicationContext())
- .getInstalledProvidersForPackage(
- getInstrumentation().getContext().getPackageName(), Process.myUserHandle())
- .get(0);
- providerInfo.provider = ComponentName.createRelative(TEST_PACKAGE, widgetClassName);
+ AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(ComponentName
+ .createRelative(TEST_PACKAGE, widgetClassName));
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo);
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderAccessibilityTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderAccessibilityTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderAccessibilityTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderAccessibilityTest.java
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
similarity index 96%
rename from tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index 8fc4481..d4e061a 100644
--- a/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.os.UserHandle;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -79,7 +80,8 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = new ActivityContextWrapper(getApplicationContext());
+ mContext = new ActivityContextWrapper(new ContextThemeWrapper(getApplicationContext(),
+ R.style.WidgetContainerTheme));
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
similarity index 92%
rename from tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 0286279..e1cc010 100644
--- a/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -16,6 +16,7 @@
package com.android.launcher3.widget.picker;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
@@ -29,6 +30,7 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.os.UserHandle;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -48,8 +50,8 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.ActivityContextWrapper;
-import com.android.launcher3.util.Executors;
import com.android.launcher3.util.WidgetUtils;
+import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetManagerHelper;
@@ -96,7 +98,8 @@
mViewHolderBinder = new WidgetsListTableViewHolderBinder(
mContext,
- LayoutInflater.from(mContext),
+ LayoutInflater.from(new ContextThemeWrapper(mContext,
+ com.android.launcher3.R.style.WidgetContainerTheme)),
mOnIconClickListener,
mOnLongClickListener);
}
@@ -110,7 +113,9 @@
TEST_PACKAGE,
/* numOfWidgets= */ 3);
mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
- Executors.MAIN_EXECUTOR.submit(() -> { }).get();
+ // Wait for the loader to complete the preview loading
+ DatabaseWidgetPreviewLoader.getLoaderExecutor().submit(() -> { }).get();
+ getInstrumentation().waitForIdleSync();
// THEN the table container has one row, which contains 3 widgets.
// View: .SampleWidget0 | .SampleWidget1 | .SampleWidget2
diff --git a/tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
diff --git a/tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
index 9c03ccf..0370a6b 100644
--- a/tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -17,6 +17,7 @@
package com.android.launcher3.widget.picker.search;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
@@ -163,7 +164,7 @@
.when(mDataProvider)
.getAllWidgets();
mSimpleWidgetsSearchAlgorithm.doSearch("Ca", mSearchCallback);
- MAIN_EXECUTOR.submit(() -> { }).get();
+ getInstrumentation().waitForIdleSync();
verify(mSearchCallback).onSearchResult(
matches("Ca"), argThat(a -> a != null && !a.isEmpty()));
}
diff --git a/tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt
new file mode 100644
index 0000000..7b629bf
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.util
+
+import android.content.ComponentName
+import android.content.Context
+import android.graphics.Point
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetPreviewContainerSizesTest {
+ private lateinit var context: Context
+ private lateinit var deviceProfile: DeviceProfile
+ private lateinit var testInvariantProfile: InvariantDeviceProfile
+ private lateinit var widgetItemInvariantProfile: InvariantDeviceProfile
+
+ @Mock private lateinit var iconCache: IconCache
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+ testInvariantProfile = LauncherAppState.getIDP(context)
+ widgetItemInvariantProfile =
+ InvariantDeviceProfile().apply {
+ numRows = TEST_GRID_SIZE
+ numColumns = TEST_GRID_SIZE
+ }
+ deviceProfile = testInvariantProfile.getDeviceProfile(context).copy(context)
+ }
+
+ @Test
+ fun widgetPreviewContainerSize_forItem_returnsCorrectContainerSize() {
+ val testSizes = getTestSizes(deviceProfile)
+ val expectedPreviewContainers = testSizes.values.toList()
+
+ for ((index, widgetSize) in testSizes.keys.withIndex()) {
+ val widgetItem =
+ createWidgetItem(widgetSize, context, widgetItemInvariantProfile, iconCache)
+
+ assertWithMessage("size for $widgetSize should be: ${expectedPreviewContainers[index]}")
+ .that(WidgetPreviewContainerSize.forItem(widgetItem, deviceProfile))
+ .isEqualTo(expectedPreviewContainers[index])
+ }
+ }
+
+ companion object {
+ private const val TEST_PACKAGE = "com.google.test"
+ private const val TEST_GRID_SIZE = 6
+
+ private val HANDHELD_TEST_SIZES: Map<Point, WidgetPreviewContainerSize> =
+ mapOf(
+ // 1x1
+ Point(1, 1) to WidgetPreviewContainerSize(1, 1),
+ // 2x1
+ Point(2, 1) to WidgetPreviewContainerSize(2, 1),
+ Point(3, 1) to WidgetPreviewContainerSize(2, 1),
+ // 4x1
+ Point(4, 1) to WidgetPreviewContainerSize(4, 1),
+ // 2x2
+ Point(2, 2) to WidgetPreviewContainerSize(2, 2),
+ Point(3, 3) to WidgetPreviewContainerSize(2, 2),
+ Point(3, 2) to WidgetPreviewContainerSize(2, 2),
+ // 2x3
+ Point(2, 3) to WidgetPreviewContainerSize(2, 3),
+ Point(3, 4) to WidgetPreviewContainerSize(2, 3),
+ Point(3, 5) to WidgetPreviewContainerSize(2, 3),
+ // 4x2
+ Point(4, 2) to WidgetPreviewContainerSize(4, 2),
+ // 4x3
+ Point(4, 3) to WidgetPreviewContainerSize(4, 3),
+ Point(4, 4) to WidgetPreviewContainerSize(4, 3),
+ )
+
+ private val TABLET_TEST_SIZES: Map<Point, WidgetPreviewContainerSize> =
+ mapOf(
+ // 1x1
+ Point(1, 1) to WidgetPreviewContainerSize(1, 1),
+ // 2x1
+ Point(2, 1) to WidgetPreviewContainerSize(2, 1),
+ // 3x1
+ Point(3, 1) to WidgetPreviewContainerSize(3, 1),
+ Point(4, 1) to WidgetPreviewContainerSize(3, 1),
+ // 2x2
+ Point(2, 2) to WidgetPreviewContainerSize(2, 2),
+ // 2x3
+ Point(2, 3) to WidgetPreviewContainerSize(2, 3),
+ // 3x2
+ Point(3, 2) to WidgetPreviewContainerSize(3, 2),
+ Point(4, 2) to WidgetPreviewContainerSize(3, 2),
+ Point(5, 2) to WidgetPreviewContainerSize(3, 2),
+ // 3x3
+ Point(3, 3) to WidgetPreviewContainerSize(3, 3),
+ Point(4, 4) to WidgetPreviewContainerSize(3, 3),
+ // 3x4
+ Point(5, 4) to WidgetPreviewContainerSize(3, 4),
+ Point(3, 4) to WidgetPreviewContainerSize(3, 4),
+ Point(5, 5) to WidgetPreviewContainerSize(3, 4),
+ Point(6, 4) to WidgetPreviewContainerSize(3, 4),
+ Point(6, 5) to WidgetPreviewContainerSize(3, 4),
+ )
+
+ private fun getTestSizes(dp: DeviceProfile) =
+ if (dp.isTablet && !dp.isTwoPanels) {
+ TABLET_TEST_SIZES
+ } else {
+ HANDHELD_TEST_SIZES
+ }
+
+ private fun createWidgetItem(
+ widgetSize: Point,
+ context: Context,
+ invariantDeviceProfile: InvariantDeviceProfile,
+ iconCache: IconCache
+ ): WidgetItem {
+ val providerInfo =
+ createAppWidgetProviderInfo(
+ ComponentName.createRelative(
+ TEST_PACKAGE,
+ /*cls=*/ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y
+ )
+ )
+ val widgetInfo =
+ LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo).apply {
+ spanX = widgetSize.x
+ spanY = widgetSize.y
+ }
+ return WidgetItem(widgetInfo, invariantDeviceProfile, iconCache, context)
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
similarity index 70%
rename from tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
index 2c5a396..b2cb266 100644
--- a/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
@@ -63,6 +63,7 @@
private static final String TEST_PACKAGE = "com.google.test";
private static final int SPACE_SIZE = 10;
+ // Note - actual widget size includes SPACE_SIZE (border) + cell padding.
private static final int CELL_SIZE = 50;
private static final int NUM_OF_COLS = 5;
private static final int NUM_OF_ROWS = 5;
@@ -105,7 +106,7 @@
@Test
- public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding0_shouldGroupWidgetsInTable() {
+ public void groupWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
@@ -113,17 +114,20 @@
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 0);
- // Row 0: 1x1(50px), 2x2(110px)
- // Row 1: 2x3(110px), 2x4(110px)
- // Row 2: 4x4(230px)
- assertThat(widgetItemInTable).hasSize(3);
- assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2);
- assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget2x4);
- assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
+ // With reordering, rows displayed in order of increasing size.
+ // Row 0: 1x1(50px)
+ // Row 1: 2x2(in a 2x2 container - 110px)
+ // Row 2: 2x3(in a 2x3 container - 110px), 2x4(in a 2x3 container - 110px)
+ // Row 3: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
+ assertThat(widgetItemInTable).hasSize(4);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
- public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding10_shouldGroupWidgetsInTable() {
+ public void groupWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding10() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
@@ -131,9 +135,13 @@
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 10);
- // Row 0: 1x1(50px), 2x2(110px)
- // Row 1: 2x3(110px), 2x4(110px)
- // Row 2: 4x4(230px)
+ // With reordering, but space taken up by cell padding, so, no grouping (even if 2x2 and 2x3
+ // use same preview container).
+ // Row 0: 1x1(50px)
+ // Row 1: 2x2(in a 2x2 container: 130px)
+ // Row 2: 2x3(in a 2x3 container: 130px)
+ // Row 3: 2x4(in a 2x3 container: 130px)
+ // Row 4: 4x4(in a 3x3 container in tablet - 190px; 4x3 on phone - 250px)
assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
@@ -143,7 +151,29 @@
}
@Test
- public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow350_cellPadding0_shouldGroupWidgetsInTable() {
+ public void groupWithReordering_widgetsOnly_maxSpanPxPerRow260_cellPadding10() {
+ List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
+ mWidget2x2);
+
+ List<ArrayList<WidgetItem>> widgetItemInTable =
+ WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
+ mTestDeviceProfile, 260, 10);
+
+ // With reordering, even with cellPadding, enough space to group 2x3 and 2x4 (which also use
+ // same container)
+ // Row 0: 1x1(50px)
+ // Row 1: 2x2(in a 2x2 container: 130px)
+ // Row 2: 2x3(in a 2x3 container: 130px), 2x4(in a 2x3 container: 130px)
+ // Row 3: 4x4(in a 3x3 container in tablet - 190px; 4x3 on phone - 250px)
+ assertThat(widgetItemInTable).hasSize(4);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
+ }
+
+ @Test
+ public void groupWithReordering_widgetsOnly_maxSpanPxPerRow350_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
@@ -151,17 +181,20 @@
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 350, 0);
- // Row 0: 1x1(50px), 2x2(110px), 2x3(110px)
- // Row 1: 2x4(110px)
- // Row 2: 4x4(230px)
- assertThat(widgetItemInTable).hasSize(3);
- assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2, mWidget2x3);
- assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x4);
- assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
+ // With reordering, rows displayed in order of increasing size.
+ // Row 0: 1x1(50px)
+ // Row 1: 2x2(in a 2x2 container: 110px)
+ // Row 2: 2x3(in a 2x3 container: 110px), 2x4(in a 2x3 container: 110px)
+ // Row 3: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
+ assertThat(widgetItemInTable).hasSize(4);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
- public void groupWidgetItemsIntoTableWithReordering_mixItems_maxSpanPxPerRow350_cellPadding0_shouldGroupWidgetsInTable() {
+ public void groupWithReordering_mixItems_maxSpanPxPerRow350_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mShortcut3, mWidget2x3, mShortcut1,
mWidget1x1, mShortcut2, mWidget2x4, mWidget2x2);
@@ -169,19 +202,22 @@
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 350, 0);
- // Row 0: 1x1(50px), 2x2(110px), 2x3(110px)
- // Row 1: 2x4(110px),
- // Row 2: 4x4(230px)
- // Row 3: shortcut3(50px), shortcut1(50px), shortcut2(50px)
- assertThat(widgetItemInTable).hasSize(4);
- assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2, mWidget2x3);
- assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x4);
- assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
- assertThat(widgetItemInTable.get(3)).containsExactly(mShortcut3, mShortcut2, mShortcut1);
+ // With reordering - rows displays in order of increasing size:
+ // Row 0: 1x1(50px)
+ // Row 1: 2x2(110px)
+ // Row 2: 2x3 (in a 2x3 container 110px), 2x4 (in a 2x3 container 110px)
+ // Row 3: 4x4 (in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
+ // Row 4: shortcut3, shortcut1, shortcut2 (shortcuts are always displayed at bottom)
+ assertThat(widgetItemInTable).hasSize(5);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
+ assertThat(widgetItemInTable.get(4)).containsExactly(mShortcut3, mShortcut2, mShortcut1);
}
@Test
- public void groupWidgetItemsIntoTableWithoutReordering_maxSpanPxPerRow220_cellPadding0_shouldMaintainTheOrder() {
+ public void groupWithoutReordering_maxSpanPxPerRow220_cellPadding0() {
List<WidgetItem> widgetItems =
List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4, mWidget2x2);
@@ -189,13 +225,19 @@
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 0);
- // Row 0: 4x4(230px)
- // Row 1: 2x3(110px), 1x1(50px)
- // Row 2: 2x4(110px), 2x2(110px)
- assertThat(widgetItemInTable).hasSize(3);
+ // Without reordering, widgets are grouped only if the next one fits and uses same preview
+ // container:
+ // Row 0: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
+ // Row 1: 2x3(in a 2x3 container - 110px)
+ // Row 2: 1x1(50px)
+ // Row 3: 2x4(in a 2x3 container - 110px)
+ // Row 4: 2x2(in a 2x2 container - 110px)
+ assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget4x4);
- assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget1x1);
- assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4, mWidget2x2);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget1x1);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mWidget2x4);
+ assertThat(widgetItemInTable.get(4)).containsExactly(mWidget2x2);
}
private void initDP() {
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/util/WidgetDragScaleUtilsTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/util/WidgetDragScaleUtilsTest.kt
new file mode 100644
index 0000000..63833e4
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/util/WidgetDragScaleUtilsTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.util
+
+import android.content.Context
+import android.graphics.Point
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.R
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.util.ActivityContextWrapper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetDragScaleUtilsTest {
+ private lateinit var context: Context
+ private lateinit var itemInfo: ItemInfo
+ private lateinit var deviceProfile: DeviceProfile
+
+ @Before
+ fun setup() {
+ context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+
+ itemInfo = ItemInfo()
+
+ deviceProfile =
+ Mockito.spy(LauncherAppState.getIDP(context).getDeviceProfile(context).copy(context))
+
+ doReturn(0.8f)
+ .whenever(deviceProfile).getWorkspaceSpringLoadScale(any(Context::class.java))
+ deviceProfile.cellLayoutBorderSpacePx = Point(CELL_SPACING, CELL_SPACING)
+ deviceProfile.widgetPadding.setEmpty()
+ }
+
+ @Test
+ fun getWidgetDragScalePx_largeDraggedView_downScaled() {
+ val minSize = context.resources.getDimensionPixelSize(
+ R.dimen.widget_drag_view_min_scale_down_size)
+ whenever(deviceProfile.cellSize).thenReturn(Point(minSize * 2, minSize * 2))
+
+ itemInfo.spanX = 2
+ itemInfo.spanY = 2
+
+ val widgetSize = WidgetSizes.getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY)
+ // Assume dragged view was a drawable which was larger than widget's size.
+ val draggedViewWidthPx = widgetSize.width + 0.5f * widgetSize.width
+ val draggedViewHeightPx = widgetSize.height + 0.5f * widgetSize.height
+ // Returns negative scale pixels - i.e. downscaled
+ assertThat(
+ WidgetDragScaleUtils.getWidgetDragScalePx(
+ context,
+ deviceProfile,
+ draggedViewWidthPx,
+ draggedViewHeightPx,
+ itemInfo
+ )
+ )
+ .isLessThan(0)
+ }
+
+ @Test
+ fun getWidgetDragScalePx_draggedViewSameAsWidgetSize_downScaled() {
+ val minSize = context.resources.getDimensionPixelSize(
+ R.dimen.widget_drag_view_min_scale_down_size)
+ whenever(deviceProfile.cellSize).thenReturn(Point(minSize * 2, minSize * 2))
+ itemInfo.spanX = 4
+ itemInfo.spanY = 2
+
+ val widgetSize = WidgetSizes.getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY)
+ // Assume dragged view was a drawable which was larger than widget's size.
+ val draggedViewWidthPx = widgetSize.width.toFloat()
+ val draggedViewHeightPx = widgetSize.height.toFloat()
+ // Returns negative scale pixels - i.e. downscaled
+ // Even if dragged view was of same size as widget's drop target, to accommodate the spring
+ // load scaling of workspace and additionally getting the view inside of drop target frame,
+ // widget would be downscaled.
+ assertThat(
+ WidgetDragScaleUtils.getWidgetDragScalePx(
+ context,
+ deviceProfile,
+ draggedViewWidthPx,
+ draggedViewHeightPx,
+ itemInfo
+ )
+ )
+ .isLessThan(0)
+ }
+
+ @Test
+ fun getWidgetDragScalePx_draggedViewSmallerThanMinSize_scaledSizeIsAtLeastMinSize() {
+ val minSizePx =
+ context.resources.getDimensionPixelSize(R.dimen.widget_drag_view_min_scale_down_size)
+ // Assume min size is greater than cell size, so that, we know the upscale of dragged view
+ // is due to min size enforcement.
+ whenever(deviceProfile.cellSize).thenReturn(Point(minSizePx / 2, minSizePx / 2))
+ itemInfo.spanX = 1
+ itemInfo.spanY = 1
+
+ val draggedViewWidthPx = minSizePx - 15f
+ val draggedViewHeightPx = minSizePx - 15f
+
+ // Returns positive scale pixels - i.e. up-scaled
+ val finalScalePx =
+ WidgetDragScaleUtils.getWidgetDragScalePx(
+ context,
+ deviceProfile,
+ draggedViewWidthPx,
+ draggedViewHeightPx,
+ itemInfo
+ )
+
+ val effectiveWidthPx = draggedViewWidthPx + finalScalePx
+ val scaleFactor = (draggedViewWidthPx + finalScalePx) / draggedViewWidthPx
+ val effectiveHeightPx = scaleFactor * draggedViewHeightPx
+ // Both original height and width were smaller than min size, scaling them down below min
+ // size would have made them not visible under the finger. Here, as expected, widget is
+ // at least as large as min size.
+ assertThat(effectiveWidthPx).isAtLeast(minSizePx)
+ assertThat(effectiveHeightPx).isAtLeast(minSizePx)
+ }
+
+ companion object {
+ const val CELL_SPACING = 10
+ }
+}
diff --git a/tests/multivalentTestsForDeviceless b/tests/multivalentTestsForDeviceless
deleted file mode 120000
index 20ee34a..0000000
--- a/tests/multivalentTestsForDeviceless
+++ /dev/null
@@ -1 +0,0 @@
-multivalentTests
\ No newline at end of file
diff --git a/tests/shared/com/android/launcher3/testing/OWNERS b/tests/shared/com/android/launcher3/testing/OWNERS
new file mode 100644
index 0000000..02e8ebc
--- /dev/null
+++ b/tests/shared/com/android/launcher3/testing/OWNERS
@@ -0,0 +1,4 @@
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/tests/src/com/android/launcher3/LauncherIntentTest.java b/tests/src/com/android/launcher3/LauncherIntentTest.java
new file mode 100644
index 0000000..aeeb42a
--- /dev/null
+++ b/tests/src/com/android/launcher3/LauncherIntentTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.platform.test.annotations.LargeTest;
+import android.view.KeyEvent;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
+import com.android.launcher3.allapps.SearchRecyclerView;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class LauncherIntentTest extends AbstractLauncherUiTest<Launcher> {
+
+ public final Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS);
+
+ @Test
+ public void testAllAppsIntent() {
+ // Try executing ALL_APPS intent
+ executeOnLauncher(launcher -> launcher.onNewIntent(allAppsIntent));
+ // A-Z view with Main adapter should be loaded
+ assertOnMainAdapterAToZView();
+
+
+ // Try Moving to search view now
+ moveToSearchView();
+ // Try executing ALL_APPS intent
+ executeOnLauncher(launcher -> launcher.onNewIntent(allAppsIntent));
+ // A-Z view with Main adapter should be loaded
+ assertOnMainAdapterAToZView();
+ }
+
+ // Highlights the search bar, then fills text to display the SearchView.
+ private void moveToSearchView() {
+ // All Apps view should be loaded
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
+ executeOnLauncher(launcher -> launcher.getAppsView().getSearchView().requestFocus());
+ // Search view should be in focus
+ waitForLauncherCondition("Search view is not in focus.",
+ launcher -> launcher.getAppsView().getSearchView().hasFocus());
+ mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_C, 0);
+ // Upon key press, search recycler view should be loaded
+ waitForLauncherCondition("Search view not active.",
+ launcher -> launcher.getAppsView().getActiveRecyclerView()
+ instanceof SearchRecyclerView);
+ mLauncher.unpressKeyCode(KeyEvent.KEYCODE_C, 0);
+ }
+
+ // Checks if main adapter view is selected, search bar is out of focus and scroller is at start.
+ private void assertOnMainAdapterAToZView() {
+ // All Apps State should be loaded
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
+
+ // A-Z recycler view should be active.
+ waitForLauncherCondition("A-Z view not active.",
+ launcher -> !(launcher.getAppsView().getActiveRecyclerView()
+ instanceof SearchRecyclerView));
+ // Personal Adapter should be selected.
+ waitForLauncherCondition("Not on Main Adapter View",
+ launcher -> launcher.getAppsView().getCurrentPage()
+ == ActivityAllAppsContainerView.AdapterHolder.MAIN);
+ // Search view should not be in focus
+ waitForLauncherCondition("Search view has focus.",
+ launcher -> !launcher.getAppsView().getSearchView().hasFocus());
+ // Scroller should be at top
+ executeOnLauncher(launcher -> assertEquals(
+ "All Apps started in already scrolled state", 0,
+ getAllAppsScroll(launcher)));
+ }
+}
diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
index 0907f8f..57117cb 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -26,10 +26,10 @@
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -39,6 +39,7 @@
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -48,6 +49,7 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.UserIconInfo;
import com.android.launcher3.util.rule.TestStabilityRule;
@@ -79,7 +81,7 @@
private PrivateProfileManager mPrivateProfileManager;
@Mock
- private ActivityAllAppsContainerView mActivityAllAppsContainerView;
+ private ActivityAllAppsContainerView mAllApps;
@Mock
private StatsLogManager mStatsLogManager;
@Mock
@@ -89,13 +91,15 @@
@Mock
private Context mContext;
@Mock
- private AllAppsStore mAllAppsStore;
+ private AllAppsStore<?> mAllAppsStore;
@Mock
private PackageManager mPackageManager;
@Mock
private LauncherApps mLauncherApps;
-
- private boolean mRunnableCalled = false;
+ @Mock
+ private AllAppsRecyclerView mAllAppsRecyclerView;
+ @Mock
+ private Resources mResources;
@Before
public void setUp() {
@@ -104,8 +108,11 @@
.thenReturn(Arrays.asList(MAIN_HANDLE, PRIVATE_HANDLE));
when(mUserCache.getUserInfo(Process.myUserHandle())).thenReturn(MAIN_ICON_INFO);
when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO);
- when(mActivityAllAppsContainerView.getContext()).thenReturn(mContext);
- when(mActivityAllAppsContainerView.getAppsStore()).thenReturn(mAllAppsStore);
+ when(mAllApps.getContext()).thenReturn(mContext);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getApplicationContext()).thenReturn(getApplicationContext());
+ when(mAllApps.getAppsStore()).thenReturn(mAllAppsStore);
+ when(mAllApps.getActiveRecyclerView()).thenReturn(mAllAppsRecyclerView);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.resolveActivity(any(), any())).thenReturn(new ResolveInfo());
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
@@ -116,14 +123,14 @@
.thenReturn("com.android.launcher3.tests.privateProfileManager");
when(mLauncherApps.getPreInstalledSystemPackages(any())).thenReturn(new ArrayList<>());
mPrivateProfileManager = new PrivateProfileManager(mUserManager,
- mActivityAllAppsContainerView, mStatsLogManager, mUserCache);
+ mAllApps, mStatsLogManager, mUserCache);
}
@Test
public void lockPrivateProfile_requestsQuietModeAsTrue() throws Exception {
when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(false);
- mPrivateProfileManager.lockPrivateProfile();
+ mPrivateProfileManager.setQuietMode(true /* lock */);
awaitTasksCompleted();
Mockito.verify(mUserManager).requestQuietModeEnabled(true, PRIVATE_HANDLE);
@@ -133,7 +140,7 @@
public void unlockPrivateProfile_requestsQuietModeAsFalse() throws Exception {
when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(true);
- mPrivateProfileManager.unlockPrivateProfile(() -> {});
+ mPrivateProfileManager.setQuietMode(false /* unlock */);
awaitTasksCompleted();
Mockito.verify(mUserManager).requestQuietModeEnabled(false, PRIVATE_HANDLE);
@@ -142,7 +149,9 @@
@Test
public void quietModeFlagPresent_privateSpaceIsResetToDisabled() {
PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
- doNothing().when(privateProfileManager).resetPrivateSpaceDecorator(anyInt());
+ doNothing().when(privateProfileManager).addPrivateSpaceDecorator(anyInt());
+ doNothing().when(privateProfileManager).executeLock();
+ doReturn(mAllAppsRecyclerView).when(privateProfileManager).getMainRecyclerView();
when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED))
.thenReturn(false, true);
@@ -158,42 +167,56 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/320703862
- public void transitioningToUnlocked_resetCallsPendingRunnable() throws Exception {
+ public void transitioningToUnlocked_resetCallsPostUnlock() throws Exception {
PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
- doNothing().when(privateProfileManager).resetPrivateSpaceDecorator(anyInt());
+ doNothing().when(privateProfileManager).addPrivateSpaceDecorator(anyInt());
+ doReturn(mAllAppsRecyclerView).when(privateProfileManager).getMainRecyclerView();
when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED))
.thenReturn(false);
+ doNothing().when(privateProfileManager).expandPrivateSpace();
when(privateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED);
- mRunnableCalled = false;
- privateProfileManager.unlockPrivateProfile(this::testRunnable);
+ privateProfileManager.setQuietMode(false /* unlock */);
privateProfileManager.reset();
awaitTasksCompleted();
- Mockito.verify(privateProfileManager).applyUnlockRunnable();
- assertTrue("Unlock Runnable not Invoked", mRunnableCalled);
+ Mockito.verify(privateProfileManager).postUnlock();
}
@Test
- public void openPrivateSpaceSettings_triggersSecurityAndPrivacyIntent() {
- Intent expectedIntent = PrivateProfileManager.PRIVATE_SPACE_INTENT;
+ public void transitioningToLocked_resetCallsExecuteLock() throws Exception {
+ PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
+ doNothing().when(privateProfileManager).addPrivateSpaceDecorator(anyInt());
+ doNothing().when(privateProfileManager).executeLock();
+ doReturn(mAllAppsRecyclerView).when(privateProfileManager).getMainRecyclerView();
+ when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED))
+ .thenReturn(true);
+ doNothing().when(privateProfileManager).expandPrivateSpace();
+ when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+
+ privateProfileManager.setQuietMode(true /* lock */);
+ privateProfileManager.reset();
+
+ awaitTasksCompleted();
+ Mockito.verify(privateProfileManager).executeLock();
+ }
+
+ @Test
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/339109319
+ public void openPrivateSpaceSettings_triggersCorrectIntent() {
+ Intent expectedIntent = ApiWrapper.INSTANCE.get(mContext).getPrivateSpaceSettingsIntent();
ArgumentCaptor<Intent> acIntent = ArgumentCaptor.forClass(Intent.class);
mPrivateProfileManager.setPrivateSpaceSettingsAvailable(true);
- mPrivateProfileManager.openPrivateSpaceSettings();
+ mContext.startActivity(expectedIntent);
Mockito.verify(mContext).startActivity(acIntent.capture());
- Intent actualIntent = acIntent.getValue();
- assertEquals("Intent Action is different", expectedIntent.getAction(),
- actualIntent.getAction());
+ assertEquals("Intent Action is different",
+ expectedIntent == null ? null : expectedIntent.toUri(0),
+ acIntent.getValue() == null ? null : acIntent.getValue().toUri(0));
}
private static void awaitTasksCompleted() throws Exception {
UI_HELPER_EXECUTOR.submit(() -> null).get();
}
-
- private void testRunnable() {
- mRunnableCalled = true;
- }
}
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
deleted file mode 100644
index 490cb47..0000000
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.allapps;
-
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
-import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
-import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
-import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.ActivityContextWrapper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class PrivateSpaceHeaderViewControllerTest {
-
- private static final int CONTAINER_HEADER_ELEMENT_COUNT = 1;
- private static final int LOCK_UNLOCK_BUTTON_COUNT = 1;
- private static final int PS_SETTINGS_BUTTON_COUNT_VISIBLE = 1;
- private static final int PS_SETTINGS_BUTTON_COUNT_INVISIBLE = 0;
- private static final int PS_TRANSITION_IMAGE_COUNT = 1;
-
- private Context mContext;
- private PrivateSpaceHeaderViewController mPsHeaderViewController;
- private RelativeLayout mPsHeaderLayout;
- @Mock
- private PrivateProfileManager mPrivateProfileManager;
- @Mock
- private ActivityAllAppsContainerView mAllApps;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = new ActivityContextWrapper(getApplicationContext());
- mPsHeaderViewController = new PrivateSpaceHeaderViewController(mAllApps,
- mPrivateProfileManager);
- mPsHeaderLayout = (RelativeLayout) LayoutInflater.from(mContext).inflate(
- R.layout.private_space_header, null);
- }
-
- @Test
- public void privateProfileDisabled_psHeaderContainsLockedView() throws Exception {
- Bitmap unlockButton = getBitmap(mContext.getDrawable(R.drawable.ic_lock));
- when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED);
-
- mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
- awaitTasksCompleted();
-
- int totalContainerHeaderView = 0;
- int totalLockUnlockButtonView = 0;
- for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
- View view = mPsHeaderLayout.getChildAt(i);
- if (view.getId() == R.id.ps_container_header) {
- totalContainerHeaderView += 1;
- assertEquals(View.VISIBLE, view.getVisibility());
- } else if (view.getId() == R.id.settingsAndLockGroup) {
- ImageView lockIcon = view.findViewById(R.id.lock_icon);
- assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(unlockButton));
- assertEquals(View.VISIBLE, lockIcon.getVisibility());
-
- // Verify textView shouldn't be showing when disabled.
- TextView lockText = view.findViewById(R.id.lock_text);
- assertEquals(View.GONE, lockText.getVisibility());
- totalLockUnlockButtonView += 1;
- } else {
- assertEquals(View.GONE, view.getVisibility());
- }
- }
- assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
- assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
- }
-
- @Test
- public void privateProfileEnabled_psHeaderContainsUnlockedView() throws Exception {
- Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.ic_lock));
- Bitmap settingsImage = getBitmap(mContext.getDrawable(R.drawable.ic_ps_settings));
- when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
- when(mPrivateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(true);
-
- mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
- awaitTasksCompleted();
-
- int totalContainerHeaderView = 0;
- int totalLockUnlockButtonView = 0;
- int totalSettingsImageView = 0;
- for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
- View view = mPsHeaderLayout.getChildAt(i);
- if (view.getId() == R.id.ps_container_header) {
- totalContainerHeaderView += 1;
- assertEquals(View.VISIBLE, view.getVisibility());
- } else if (view.getId() == R.id.settingsAndLockGroup) {
- // Look for settings button.
- ImageButton settingsButton = view.findViewById(R.id.ps_settings_button);
- assertEquals(View.VISIBLE, settingsButton.getVisibility());
- totalSettingsImageView += 1;
- assertTrue(getBitmap(settingsButton.getDrawable()).sameAs(settingsImage));
-
- // Look for lock_icon and lock_text.
- ImageView lockIcon = view.findViewById(R.id.lock_icon);
- assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(lockImage));
- assertEquals(View.VISIBLE, lockIcon.getVisibility());
- TextView lockText = view.findViewById(R.id.lock_text);
- assertEquals(View.VISIBLE, lockText.getVisibility());
- totalLockUnlockButtonView += 1;
- } else {
- assertEquals(View.GONE, view.getVisibility());
- }
- }
- assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
- assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
- assertEquals(PS_SETTINGS_BUTTON_COUNT_VISIBLE, totalSettingsImageView);
- }
-
- @Test
- public void privateProfileEnabledAndNoSettingsIntent_psHeaderContainsUnlockedView()
- throws Exception {
- Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.ic_lock));
- when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
- when(mPrivateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(false);
-
- mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
- awaitTasksCompleted();
-
- int totalContainerHeaderView = 0;
- int totalLockUnlockButtonView = 0;
- int totalSettingsImageView = 0;
- for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
- View view = mPsHeaderLayout.getChildAt(i);
- if (view.getId() == R.id.ps_container_header) {
- totalContainerHeaderView += 1;
- assertEquals(View.VISIBLE, view.getVisibility());
- } else if (view.getId() == R.id.settingsAndLockGroup) {
- // Ensure there is no settings button.
- ImageButton settingsImage = view.findViewById(R.id.ps_settings_button);
- assertEquals(View.GONE, settingsImage.getVisibility());
-
- // Check lock icon and lock text is there.
- ImageView lockIcon = view.findViewById(R.id.lock_icon);
- assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(lockImage));
- assertEquals(View.VISIBLE, lockIcon.getVisibility());
- TextView lockText = view.findViewById(R.id.lock_text);
- assertEquals(View.VISIBLE, lockText.getVisibility());
- totalLockUnlockButtonView += 1;
- } else {
- assertEquals(View.GONE, view.getVisibility());
- }
- }
- assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
- assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
- assertEquals(PS_SETTINGS_BUTTON_COUNT_INVISIBLE, totalSettingsImageView);
- }
-
- @Test
- public void privateProfileTransitioning_psHeaderContainsTransitionView() throws Exception {
- Bitmap transitionImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_transition_image));
- when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_TRANSITION);
-
- mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
- awaitTasksCompleted();
-
- int totalContainerHeaderView = 0;
- int totalLockUnlockButtonView = 0;
- for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
- View view = mPsHeaderLayout.getChildAt(i);
- if (view.getId() == R.id.ps_container_header) {
- totalContainerHeaderView += 1;
- assertEquals(View.VISIBLE, view.getVisibility());
- } else if (view.getId() == R.id.ps_transition_image
- && view instanceof ImageView imageView) {
- totalLockUnlockButtonView += 1;
- assertEquals(View.VISIBLE, view.getVisibility());
- assertTrue(getBitmap(imageView.getDrawable()).sameAs(transitionImage));
- } else if (view.getId() == R.id.settingsAndLockGroup) {
- LinearLayout lockUnlockButton = view.findViewById(R.id.ps_lock_unlock_button);
- assertEquals(View.GONE, lockUnlockButton.getVisibility());
- } else {
- assertEquals(View.GONE, view.getVisibility());
- }
- }
- assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
- assertEquals(PS_TRANSITION_IMAGE_COUNT, totalLockUnlockButtonView);
- }
-
- private Bitmap getBitmap(Drawable drawable) {
- Bitmap result;
- if (drawable instanceof BitmapDrawable) {
- result = ((BitmapDrawable) drawable).getBitmap();
- } else {
- int width = drawable.getIntrinsicWidth();
- int height = drawable.getIntrinsicHeight();
- // Some drawables have no intrinsic width - e.g. solid colours.
- if (width <= 0) {
- width = 1;
- }
- if (height <= 0) {
- height = 1;
- }
-
- result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(result);
- drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
- drawable.draw(canvas);
- }
- return result;
- }
-
- private static void awaitTasksCompleted() throws Exception {
- UI_HELPER_EXECUTOR.submit(() -> null).get();
- }
-}
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
new file mode 100644
index 0000000..eac7f63
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.answer;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.R;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.UserIconInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PrivateSpaceHeaderViewTest {
+
+ private static final UserHandle MAIN_HANDLE = Process.myUserHandle();
+ private static final UserHandle PRIVATE_HANDLE = new UserHandle(11);
+ private static final UserIconInfo MAIN_ICON_INFO =
+ new UserIconInfo(MAIN_HANDLE, UserIconInfo.TYPE_MAIN);
+ private static final UserIconInfo PRIVATE_ICON_INFO =
+ new UserIconInfo(PRIVATE_HANDLE, UserIconInfo.TYPE_PRIVATE);
+ private static final String CAMERA_PACKAGE_NAME = "com.android.launcher3.tests.camera";
+ private static final int CONTAINER_HEADER_ELEMENT_COUNT = 1;
+ private static final int LOCK_UNLOCK_BUTTON_COUNT = 1;
+ private static final int PS_SETTINGS_BUTTON_COUNT_VISIBLE = 1;
+ private static final int PS_SETTINGS_BUTTON_COUNT_INVISIBLE = 0;
+ private static final int PS_TRANSITION_IMAGE_COUNT = 1;
+ private static final int NUM_APP_COLS = 4;
+ private static final int NUM_PRIVATE_SPACE_APPS = 50;
+ private static final int ALL_APPS_HEIGHT = 10;
+ private static final int ALL_APPS_CELL_HEIGHT = 1;
+ private static final int PS_HEADER_HEIGHT = 1;
+ private static final int BIGGER_PS_HEADER_HEIGHT = 2;
+ private static final int SCROLL_NO_WHERE = -1;
+ private static final float HEADER_PROTECTION_HEIGHT = 1F;
+
+ private Context mContext;
+ private RelativeLayout mPsHeaderLayout;
+ private AlphabeticalAppsList<?> mAlphabeticalAppsList;
+ private PrivateProfileManager mPrivateProfileManager;
+ @Mock
+ private ActivityAllAppsContainerView mAllApps;
+ @Mock
+ private AllAppsStore<?> mAllAppsStore;
+ @Mock
+ private UserCache mUserCache;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private StatsLogManager mStatsLogManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = new ActivityContextWrapper(getApplicationContext());
+ when(mAllApps.getContext()).thenReturn(mContext);
+ when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO);
+ when(mUserCache.getUserProfiles())
+ .thenReturn(Arrays.asList(MAIN_HANDLE, PRIVATE_HANDLE));
+ when(mUserCache.getUserInfo(Process.myUserHandle())).thenReturn(MAIN_ICON_INFO);
+ mPrivateProfileManager = new PrivateProfileManager(mUserManager,
+ mAllApps, mStatsLogManager, mUserCache);
+ mPsHeaderLayout = (RelativeLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.private_space_header, null);
+ }
+
+ @Test
+ public void privateProfileDisabled_psHeaderContainsLockedView() throws Exception {
+ Bitmap unlockButton = getBitmap(mContext.getDrawable(R.drawable.ic_lock));
+ PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
+ when(privateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED);
+ privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+ awaitTasksCompleted();
+
+ int totalContainerHeaderView = 0;
+ int totalLockUnlockButtonView = 0;
+ for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+ View view = mPsHeaderLayout.getChildAt(i);
+ if (view.getId() == R.id.ps_container_header) {
+ totalContainerHeaderView += 1;
+ assertEquals(View.VISIBLE, view.getVisibility());
+ } else if (view.getId() == R.id.settingsAndLockGroup) {
+ ImageView lockIcon = view.findViewById(R.id.lock_icon);
+ assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(unlockButton));
+ assertEquals(View.VISIBLE, lockIcon.getVisibility());
+
+ // Verify textView shouldn't be showing when disabled.
+ TextView lockText = view.findViewById(R.id.lock_text);
+ assertEquals(View.GONE, lockText.getVisibility());
+ totalLockUnlockButtonView += 1;
+ } else {
+ assertEquals(View.GONE, view.getVisibility());
+ }
+ }
+
+ assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+ assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
+ }
+
+ @Test
+ public void privateProfileEnabled_psHeaderContainsUnlockedView() throws Exception {
+ Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.ic_lock));
+ Bitmap settingsImage = getBitmap(mContext.getDrawable(R.drawable.ic_ps_settings));
+ PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
+ when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+ when(privateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(true);
+ privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+ awaitTasksCompleted();
+
+ int totalContainerHeaderView = 0;
+ int totalLockUnlockButtonView = 0;
+ int totalSettingsImageView = 0;
+ for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+ View view = mPsHeaderLayout.getChildAt(i);
+ if (view.getId() == R.id.ps_container_header) {
+ totalContainerHeaderView += 1;
+ assertEquals(View.VISIBLE, view.getVisibility());
+ } else if (view.getId() == R.id.settingsAndLockGroup) {
+ // Look for settings button.
+ ImageButton settingsButton = view.findViewById(R.id.ps_settings_button);
+ assertEquals(View.VISIBLE, settingsButton.getVisibility());
+ totalSettingsImageView += 1;
+ assertTrue(getBitmap(settingsButton.getDrawable()).sameAs(settingsImage));
+
+ // Look for lock_icon and lock_text.
+ ImageView lockIcon = view.findViewById(R.id.lock_icon);
+ assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(lockImage));
+ assertEquals(View.VISIBLE, lockIcon.getVisibility());
+ TextView lockText = view.findViewById(R.id.lock_text);
+ assertEquals(View.VISIBLE, lockText.getVisibility());
+ totalLockUnlockButtonView += 1;
+ } else {
+ assertEquals(View.GONE, view.getVisibility());
+ }
+ }
+
+ assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+ assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
+ assertEquals(PS_SETTINGS_BUTTON_COUNT_VISIBLE, totalSettingsImageView);
+ }
+
+ @Test
+ public void privateProfileEnabledAndNoSettingsIntent_psHeaderContainsUnlockedView()
+ throws Exception {
+ Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.ic_lock));
+ PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
+ when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+ when(privateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(false);
+ privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+ awaitTasksCompleted();
+
+ int totalContainerHeaderView = 0;
+ int totalLockUnlockButtonView = 0;
+ int totalSettingsImageView = 0;
+ for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+ View view = mPsHeaderLayout.getChildAt(i);
+ if (view.getId() == R.id.ps_container_header) {
+ totalContainerHeaderView += 1;
+ assertEquals(View.VISIBLE, view.getVisibility());
+ } else if (view.getId() == R.id.settingsAndLockGroup) {
+ // Ensure there is no settings button.
+ ImageButton settingsImage = view.findViewById(R.id.ps_settings_button);
+ assertEquals(View.GONE, settingsImage.getVisibility());
+
+ // Check lock icon and lock text is there.
+ ImageView lockIcon = view.findViewById(R.id.lock_icon);
+ assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(lockImage));
+ assertEquals(View.VISIBLE, lockIcon.getVisibility());
+ TextView lockText = view.findViewById(R.id.lock_text);
+ assertEquals(View.VISIBLE, lockText.getVisibility());
+ totalLockUnlockButtonView += 1;
+ } else {
+ assertEquals(View.GONE, view.getVisibility());
+ }
+ }
+
+ assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+ assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
+ assertEquals(PS_SETTINGS_BUTTON_COUNT_INVISIBLE, totalSettingsImageView);
+ }
+
+ @Test
+ public void privateProfileTransitioning_psHeaderContainsTransitionView() throws Exception {
+ Bitmap transitionImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_transition_image));
+ PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
+ when(privateProfileManager.getCurrentState()).thenReturn(STATE_TRANSITION);
+ privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+ awaitTasksCompleted();
+
+ int totalContainerHeaderView = 0;
+ int totalLockUnlockButtonView = 0;
+ for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+ View view = mPsHeaderLayout.getChildAt(i);
+ if (view.getId() == R.id.ps_container_header) {
+ totalContainerHeaderView += 1;
+ assertEquals(View.VISIBLE, view.getVisibility());
+ } else if (view.getId() == R.id.ps_transition_image
+ && view instanceof ImageView imageView) {
+ totalLockUnlockButtonView += 1;
+ assertEquals(View.VISIBLE, view.getVisibility());
+ assertTrue(getBitmap(imageView.getDrawable()).sameAs(transitionImage));
+ } else if (view.getId() == R.id.settingsAndLockGroup) {
+ LinearLayout lockUnlockButton = view.findViewById(R.id.ps_lock_unlock_button);
+ assertEquals(View.GONE, lockUnlockButton.getVisibility());
+ } else {
+ assertEquals(View.GONE, view.getVisibility());
+ }
+ }
+
+ assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+ assertEquals(PS_TRANSITION_IMAGE_COUNT, totalLockUnlockButtonView);
+ }
+
+ @Test
+ public void scrollForViewToBeVisibleInContainer_withHeader() {
+ when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+ PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
+ when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+ doReturn(splitIntoUserInstalledAndSystemApps()).when(privateProfileManager)
+ .splitIntoUserInstalledAndSystemApps(any());
+ doReturn(0).when(privateProfileManager).addPrivateSpaceHeader(any());
+ doAnswer(answer(this::addPrivateSpaceHeader)).when(privateProfileManager)
+ .addPrivateSpaceHeader(any());
+ doNothing().when(privateProfileManager).addPrivateSpaceInstallAppButton(any());
+ doReturn(0).when(privateProfileManager).addSystemAppsDivider(any());
+ when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+ when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+ when(mAllApps.isUsingTabs()).thenReturn(true);
+ mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
+ null, privateProfileManager);
+ mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS);
+ mAlphabeticalAppsList.updateItemFilter(info -> info != null
+ && info.user.equals(MAIN_HANDLE));
+
+ int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
+ int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+
+ // The number of adapterItems should be the private space apps + one main app + header.
+ assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1,
+ mAlphabeticalAppsList.getAdapterItems().size());
+ assertEquals(position,
+ privateProfileManager.scrollForHeaderToBeVisibleInContainer(
+ new AllAppsRecyclerView(mContext),
+ mAlphabeticalAppsList.getAdapterItems(),
+ PS_HEADER_HEIGHT,
+ ALL_APPS_CELL_HEIGHT));
+ }
+
+ @Test
+ public void scrollForViewToBeVisibleInContainer_withHeaderNoTabs() {
+ when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+ PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
+ when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+ doReturn(splitIntoUserInstalledAndSystemApps()).when(privateProfileManager)
+ .splitIntoUserInstalledAndSystemApps(any());
+ doReturn(0).when(privateProfileManager).addPrivateSpaceHeader(any());
+ doAnswer(answer(this::addPrivateSpaceHeader)).when(privateProfileManager)
+ .addPrivateSpaceHeader(any());
+ doNothing().when(privateProfileManager).addPrivateSpaceInstallAppButton(any());
+ doReturn(0).when(privateProfileManager).addSystemAppsDivider(any());
+ when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+ when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+ when(mAllApps.isUsingTabs()).thenReturn(false);
+ mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
+ null, privateProfileManager);
+ mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS);
+ mAlphabeticalAppsList.updateItemFilter(info -> info != null
+ && info.user.equals(MAIN_HANDLE));
+
+ int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT) - 1;
+ int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+
+ // The number of adapterItems should be the private space apps + one main app + header.
+ assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1,
+ mAlphabeticalAppsList.getAdapterItems().size());
+ assertEquals(position,
+ privateProfileManager.scrollForHeaderToBeVisibleInContainer(
+ new AllAppsRecyclerView(mContext),
+ mAlphabeticalAppsList.getAdapterItems(),
+ PS_HEADER_HEIGHT,
+ ALL_APPS_CELL_HEIGHT));
+ }
+
+ @Test
+ public void scrollForViewToBeVisibleInContainer_withHeaderAndLessAppRowSpace() {
+ when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+ PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
+ when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+ doReturn(splitIntoUserInstalledAndSystemApps()).when(privateProfileManager)
+ .splitIntoUserInstalledAndSystemApps(any());
+ doReturn(0).when(privateProfileManager).addPrivateSpaceHeader(any());
+ doAnswer(answer(this::addPrivateSpaceHeader)).when(privateProfileManager)
+ .addPrivateSpaceHeader(any());
+ doNothing().when(privateProfileManager).addPrivateSpaceInstallAppButton(any());
+ doReturn(0).when(privateProfileManager).addSystemAppsDivider(any());
+ when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+ when(mAllApps.isUsingTabs()).thenReturn(true);
+ when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+ mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
+ null, privateProfileManager);
+ mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS);
+ mAlphabeticalAppsList.updateItemFilter(info -> info != null
+ && info.user.equals(MAIN_HANDLE));
+
+ int rows = (int) (ALL_APPS_HEIGHT - BIGGER_PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
+ int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+
+ // The number of adapterItems should be the private space apps + one main app + header.
+ assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1,
+ mAlphabeticalAppsList.getAdapterItems().size());
+ assertEquals(position,
+ privateProfileManager.scrollForHeaderToBeVisibleInContainer(
+ new AllAppsRecyclerView(mContext),
+ mAlphabeticalAppsList.getAdapterItems(),
+ BIGGER_PS_HEADER_HEIGHT,
+ ALL_APPS_CELL_HEIGHT));
+ }
+
+ @Test
+ public void scrollForViewToBeVisibleInContainer_withNoHeader() {
+ when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+ PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
+ when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+ doReturn(splitIntoUserInstalledAndSystemApps()).when(privateProfileManager)
+ .splitIntoUserInstalledAndSystemApps(any());
+ doReturn(0).when(privateProfileManager).addPrivateSpaceHeader(any());
+ doNothing().when(privateProfileManager).addPrivateSpaceInstallAppButton(any());
+ doReturn(0).when(privateProfileManager).addSystemAppsDivider(any());
+ when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+ when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+ mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
+ null, privateProfileManager);
+ mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS);
+ mAlphabeticalAppsList.updateItemFilter(info -> info != null
+ && info.user.equals(MAIN_HANDLE));
+
+ // The number of adapterItems should be the private space apps + one main app.
+ assertEquals(NUM_PRIVATE_SPACE_APPS + 1,
+ mAlphabeticalAppsList.getAdapterItems().size());
+ assertEquals(SCROLL_NO_WHERE, privateProfileManager.scrollForHeaderToBeVisibleInContainer(
+ new AllAppsRecyclerView(mContext),
+ mAlphabeticalAppsList.getAdapterItems(),
+ BIGGER_PS_HEADER_HEIGHT,
+ ALL_APPS_CELL_HEIGHT));
+ }
+
+ private Bitmap getBitmap(Drawable drawable) {
+ Bitmap result;
+ if (drawable instanceof BitmapDrawable) {
+ result = ((BitmapDrawable) drawable).getBitmap();
+ } else {
+ int width = drawable.getIntrinsicWidth();
+ int height = drawable.getIntrinsicHeight();
+ // Some drawables have no intrinsic width - e.g. solid colours.
+ if (width <= 0) {
+ width = 1;
+ }
+ if (height <= 0) {
+ height = 1;
+ }
+
+ result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(result);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ }
+ return result;
+ }
+
+ private static void awaitTasksCompleted() throws Exception {
+ UI_HELPER_EXECUTOR.submit(() -> null).get();
+ }
+
+ private int addPrivateSpaceHeader(List<BaseAllAppsAdapter.AdapterItem> adapterItemList) {
+ BaseAllAppsAdapter.AdapterItem privateSpaceHeader =
+ new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER);
+ adapterItemList.add(privateSpaceHeader);
+ return adapterItemList.size();
+ }
+
+ private AppInfo[] createAppInfoList() {
+ List<AppInfo> appInfos = new ArrayList<>();
+ ComponentName gmailComponentName = new ComponentName(mContext,
+ "com.android.launcher3.tests.Activity" + "Gmail");
+ AppInfo gmailAppInfo = new
+ AppInfo(gmailComponentName, "Gmail", MAIN_HANDLE, new Intent());
+ appInfos.add(gmailAppInfo);
+ ComponentName privateCameraComponentName = new ComponentName(
+ CAMERA_PACKAGE_NAME, "CameraActivity");
+ for (int i = 0; i < NUM_PRIVATE_SPACE_APPS; i++) {
+ AppInfo privateCameraAppInfo = new AppInfo(privateCameraComponentName,
+ "Private Camera " + i, PRIVATE_HANDLE, new Intent());
+ appInfos.add(privateCameraAppInfo);
+ }
+ return appInfos.toArray(AppInfo[]::new);
+ }
+
+ private Predicate<AppInfo> splitIntoUserInstalledAndSystemApps() {
+ return iteminfo -> iteminfo.componentName == null
+ || !iteminfo.componentName.getPackageName()
+ .equals(CAMERA_PACKAGE_NAME);
+ }
+}
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java
new file mode 100644
index 0000000..9537e1c
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.allapps;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PRIVATESPACE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.util.ActivityContextWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class PrivateSpaceSettingsButtonTest {
+
+ private PrivateSpaceSettingsButton mVut;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Context context = new ActivityContextWrapper(getApplicationContext());
+ mVut = new PrivateSpaceSettingsButton(context);
+ }
+
+ @Test
+ public void privateSpaceSettingsAppInfo_hasCorrectIdAndContainer() {
+ AppInfo appInfo = mVut.createPrivateSpaceSettingsAppInfo();
+
+ assertThat(appInfo.id).isEqualTo(CONTAINER_PRIVATESPACE);
+ assertThat(appInfo.container).isEqualTo(CONTAINER_PRIVATESPACE);
+ }
+}
diff --git a/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java b/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
index 10e9f8a..0b233e5 100644
--- a/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
+++ b/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.tapl.AppIcon;
import com.android.launcher3.tapl.HomeAllApps;
@@ -30,7 +31,7 @@
* The test runs in Out of process (Oop) and in process.
* Makes sure the basic behaviors of Icons on AllApps are working.
*/
-public class TaplAllAppsIconsWorkingTest extends AbstractLauncherUiTest {
+public class TaplAllAppsIconsWorkingTest extends AbstractLauncherUiTest<Launcher> {
/**
* Makes sure we can launch an icon from All apps
diff --git a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
index 0360470..4e627a9 100644
--- a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
+++ b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
@@ -26,6 +26,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.tapl.HomeAllApps;
import com.android.launcher3.ui.AbstractLauncherUiTest;
@@ -37,7 +38,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class TaplKeyboardFocusTest extends AbstractLauncherUiTest {
+public class TaplKeyboardFocusTest extends AbstractLauncherUiTest<Launcher> {
@Test
public void testAllAppsFocusApp() {
@@ -74,7 +75,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/311410127
public void testAllAppsExitSearchAndFocusSearchResults() {
final HomeAllApps allApps = mLauncher.goHome().switchToAllApps();
assertTrue("Launcher internal state is not All Apps",
diff --git a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
index 4d73f7a..05a1224 100644
--- a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
+++ b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.content.Intent;
@@ -29,7 +28,7 @@
import androidx.test.filters.FlakyTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.launcher3.Flags;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.tapl.AllApps;
import com.android.launcher3.ui.AbstractLauncherUiTest;
@@ -42,7 +41,7 @@
* Test that we can open and close the all apps in multiple situations.
* The test runs in Out of process (Oop) and in process.
*/
-public class TaplOpenCloseAllAppsTest extends AbstractLauncherUiTest {
+public class TaplOpenCloseAllAppsTest extends AbstractLauncherUiTest<Launcher> {
public static final String READ_DEVICE_CONFIG_PERMISSION =
"android.permission.READ_DEVICE_CONFIG";
@@ -121,7 +120,6 @@
@Test
@PortraitLandscape
@PlatinumTest(focusArea = "launcher")
- @ScreenRecordRule.ScreenRecord // b/322228038
public void testAllAppsFromHome() {
// Test opening all apps
assertNotNull("switchToAllApps() returned null",
@@ -199,7 +197,6 @@
public void testPressBackFromAllAppsToHome() {
InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
READ_DEVICE_CONFIG_PERMISSION);
- assumeFalse(Flags.enablePredictiveBackGesture());
mLauncher
.getWorkspace()
.switchToAllApps()
diff --git a/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
index c69d1b9..55d3252 100644
--- a/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
+++ b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
@@ -38,7 +38,7 @@
* Tests the AppIconMenu (the menu that appears when you long press an app icon) and also make sure
* we can launch a shortcut from it.
*/
-public class TaplAppIconMenuTest extends AbstractLauncherUiTest {
+public class TaplAppIconMenuTest extends AbstractLauncherUiTest<Launcher> {
private boolean isOptionsPopupVisible(Launcher launcher) {
final ArrowPopup<?> popup = launcher.getOptionsPopup();
diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
new file mode 100644
index 0000000..479b201
--- /dev/null
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.backuprestore
+
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.model.ModelDbController
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.TestUtil
+import com.android.launcher3.util.rule.BackAndRestoreRule
+import com.android.launcher3.util.rule.setFlags
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Makes sure to test {@code RestoreDbTask#removeOldDBs}, we need to remove all the dbs that are not
+ * the last one used when we restore the device.
+ */
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class BackupAndRestoreDBSelectionTest {
+
+ @JvmField @Rule var backAndRestoreRule = BackAndRestoreRule()
+
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+ @Before
+ fun setUp() {
+ setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_NARROW_GRID_RESTORE)
+ }
+
+ @Test
+ fun oldDatabasesNotPresentAfterRestore() {
+ val dbController = ModelDbController(getInstrumentation().targetContext)
+ dbController.tryMigrateDB(null)
+ TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
+ assert(backAndRestoreRule.getDatabaseFiles().size == 1) {
+ "There should only be one database after restoring, the last one used. Actual databases ${backAndRestoreRule.getDatabaseFiles()}"
+ }
+ assert(
+ !LauncherPrefs.get(getInstrumentation().targetContext)
+ .has(LauncherPrefs.RESTORE_DEVICE)
+ ) {
+ "RESTORE_DEVICE shouldn't be present after a backup and restore."
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java b/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java
index aca9765..28a1325 100644
--- a/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java
+++ b/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java
@@ -30,6 +30,7 @@
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.MultipageCellLayout;
import com.android.launcher3.celllayout.board.CellLayoutBoard;
@@ -58,7 +59,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class TaplReorderWidgetsTest extends AbstractLauncherUiTest {
+public class TaplReorderWidgetsTest extends AbstractLauncherUiTest<Launcher> {
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt b/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
new file mode 100644
index 0000000..a006fd7
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.celllayout.testgenerator
+
+import android.graphics.Point
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.celllayout.board.CellLayoutBoard
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.gridmigration.WorkspaceItem
+import java.util.Random
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * Generate a list of WorkspaceItem's for the given test case.
+ *
+ * @param repeatAfter a number after which we would repeat the same number of icons and widgets to
+ * account for cases where the user have the same item multiple times.
+ */
+fun generateItemsForTest(
+ boards: List<CellLayoutBoard>,
+ repeatAfterRange: Point
+): List<WorkspaceItem> {
+ val id = AtomicInteger(0)
+ val widgetId = AtomicInteger(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - 1)
+ // Repeat the same appWidgetProvider and intent to have repeating widgets and icons and test
+ // that case too
+ val getIntent = { i: Int -> "Intent ${(i + repeatAfterRange.x) % repeatAfterRange.y}" }
+ val getProvider = { i: Int ->
+ "com.test/test.Provider${(i + repeatAfterRange.x) % repeatAfterRange.y }"
+ }
+ val hotseatEntries =
+ (0 until boards[0].width).map {
+ WorkspaceItem(
+ x = it,
+ y = 0,
+ spanX = 1,
+ spanY = 1,
+ id = id.getAndAdd(1),
+ screenId = it,
+ title = "Hotseat ${id.get()}",
+ appWidgetId = -1,
+ appWidgetProvider = "Hotseat icons don't have a provider",
+ intent = getIntent(id.get()),
+ type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION,
+ container = LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ )
+ }
+ var widgetEntries =
+ boards
+ .flatMapIndexed { i, board -> board.widgets.map { Pair(i, it) } }
+ .map {
+ WorkspaceItem(
+ x = it.second.cellX,
+ y = it.second.cellY,
+ spanX = it.second.spanX,
+ spanY = it.second.spanY,
+ id = id.getAndAdd(1),
+ screenId = it.first,
+ title = "Title Widget ${id.get()}",
+ appWidgetId = widgetId.getAndAdd(-1),
+ appWidgetProvider = getProvider(id.get()),
+ intent = "Widgets don't have intent",
+ type = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET,
+ container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+ )
+ }
+ widgetEntries = widgetEntries.filter { it.appWidgetProvider.contains("Provider4") }
+ val iconEntries =
+ boards
+ .flatMapIndexed { i, board -> board.icons.map { Pair(i, it) } }
+ .map {
+ WorkspaceItem(
+ x = it.second.coord.x,
+ y = it.second.coord.y,
+ spanX = 1,
+ spanY = 1,
+ id = id.getAndAdd(1),
+ screenId = it.first,
+ title = "Title Icon ${id.get()}",
+ appWidgetId = -1,
+ appWidgetProvider = "Icons don't have providers",
+ intent = getIntent(id.get()),
+ type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION,
+ container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+ )
+ }
+ return widgetEntries + hotseatEntries + iconEntries
+}
+
+data class GridMigrationUnitTestCase(
+ val boards: List<CellLayoutBoard>,
+ val destBoards: List<CellLayoutBoard>,
+ val srcSize: Point,
+ val targetSize: Point,
+ val seed: Long
+)
+
+class ValidGridMigrationTestCaseGenerator(private val generator: Random) :
+ DeterministicRandomGenerator(generator) {
+
+ companion object {
+ const val MAX_BOARD_SIZE = 12
+ const val MAX_BOARD_COUNT = 10
+ const val SEED = 10342
+ }
+
+ private fun generateBoards(
+ boardGenerator: RandomBoardGenerator,
+ width: Int,
+ height: Int,
+ boardCount: Int
+ ): List<CellLayoutBoard> {
+ val boards = mutableListOf<CellLayoutBoard>()
+ for (i in 0 until boardCount) {
+ boards.add(
+ boardGenerator.generateBoard(
+ width,
+ height,
+ boardGenerator.getRandom(0, width * height)
+ )
+ )
+ }
+ return boards
+ }
+
+ fun generateTestCase(isDestEmpty: Boolean): GridMigrationUnitTestCase {
+ val seed = generator.nextLong()
+ val randomBoardGenerator = RandomBoardGenerator(Random(seed))
+ val width = randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
+ val height = randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
+ val targetSize =
+ Point(
+ randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE),
+ randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
+ )
+ val destBoards =
+ if (isDestEmpty) {
+ listOf()
+ } else {
+ generateBoards(
+ boardGenerator = randomBoardGenerator,
+ width = targetSize.x,
+ height = targetSize.y,
+ boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT)
+ )
+ }
+ return GridMigrationUnitTestCase(
+ boards =
+ generateBoards(
+ boardGenerator = randomBoardGenerator,
+ width = width,
+ height = height,
+ boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT)
+ ),
+ destBoards = destBoards,
+ srcSize = Point(width, height),
+ targetSize = targetSize,
+ seed = seed
+ )
+ }
+}
diff --git a/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
index 7ed0fa5..1500538 100644
--- a/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
@@ -52,7 +52,7 @@
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplPromiseIconUiTest extends AbstractLauncherUiTest {
+public class TaplPromiseIconUiTest extends AbstractLauncherUiTest<Launcher> {
@Rule
public final CheckFlagsRule mCheckFlagsRule =
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index bf1ba39..41abcf8 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -21,8 +21,6 @@
import static com.android.launcher3.util.TestConstants.AppNames.PHOTOS_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -33,6 +31,7 @@
import android.platform.test.annotations.PlatinumTest;
import android.util.Log;
+import com.android.launcher3.Launcher;
import com.android.launcher3.tapl.Folder;
import com.android.launcher3.tapl.FolderIcon;
import com.android.launcher3.tapl.HomeAllApps;
@@ -42,8 +41,7 @@
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
-import com.android.launcher3.util.rule.TestStabilityRule;
+import com.android.launcher3.util.rule.ScreenRecordRule;
import org.junit.Test;
@@ -57,7 +55,7 @@
* * Can drag an icon from AllApps into the workspace
* * Can drag an icon on the Workspace to other positions of the Workspace.
*/
-public class TaplDragTest extends AbstractLauncherUiTest {
+public class TaplDragTest extends AbstractLauncherUiTest<Launcher> {
/**
* Adds two icons to the Workspace and combines them into a folder, then makes sure the icons
@@ -66,9 +64,6 @@
*/
@Test
@PortraitLandscape
- @ScreenRecord
- // Staging; will be promoted to presubmit if stable
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
@PlatinumTest(focusArea = "launcher")
public void testDragToFolder() {
// TODO: add the use case to drag an icon to an existing folder. Currently it either fails
@@ -200,6 +195,7 @@
@PlatinumTest(focusArea = "launcher")
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/343953783
public void testDragAppIcon() {
final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
@@ -227,24 +223,7 @@
public void testDragAppIconToMultipleWorkspaceCells() throws Exception {
long startTime, endTime, elapsedTime;
Point[] targets = TestUtil.getCornersAndCenterPositions(mLauncher);
-
- for (Point target : targets) {
- startTime = SystemClock.uptimeMillis();
- final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
- allApps.freeze();
- try {
- allApps.getAppIcon(TEST_APP_NAME).dragToWorkspace(target.x, target.y);
- } finally {
- allApps.unfreeze();
- }
- // Reset the workspace for the next shortcut creation.
- reinitializeLauncherData(true);
- endTime = SystemClock.uptimeMillis();
- elapsedTime = endTime - startTime;
- Log.d("testDragAppIconToWorkspaceCellTime",
- "Milliseconds taken to drag app icon to workspace cell: " + elapsedTime);
- }
-
+ reinitializeLauncherData(true);
// test to move a shortcut to other cell.
final HomeAppIcon launcherTestAppIcon = createShortcutInCenterIfNotExist(TEST_APP_NAME);
for (Point target : targets) {
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index e21309f..362596c 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
import static com.android.launcher3.testing.shared.TestProtocol.UIOBJECT_STALE_ELEMENT;
-import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
import static com.android.launcher3.util.TestConstants.AppNames.DUMMY_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
@@ -32,6 +31,7 @@
import android.platform.test.annotations.PlatinumTest;
import android.util.Log;
+import com.android.launcher3.Launcher;
import com.android.launcher3.tapl.HomeAllApps;
import com.android.launcher3.tapl.HomeAppIcon;
import com.android.launcher3.tapl.Workspace;
@@ -39,6 +39,7 @@
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 com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Test;
@@ -51,7 +52,7 @@
* Test runs in Out of process (Oop) and In process (Ipc)
* Test the behaviour of uninstalling and removing apps both from AllApps, Workspace and Hotseat.
*/
-public class TaplUninstallRemoveTest extends AbstractLauncherUiTest {
+public class TaplUninstallRemoveTest extends AbstractLauncherUiTest<Launcher> {
/**
* Deletes app both built-in and user-installed from the Workspace and makes sure it's no longer
@@ -114,7 +115,6 @@
@Test
@PortraitLandscape
@PlatinumTest(focusArea = "launcher")
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/326130648
public void testUninstallFromAllApps() throws Exception {
installDummyAppAndWaitForUIUpdate();
try {
@@ -132,6 +132,7 @@
*/
@Test
@PlatinumTest(focusArea = "launcher")
+ @ScreenRecordRule.ScreenRecord // b/319501259
public void uninstallWorkspaceIcon() throws IOException {
Point[] gridPositions = TestUtil.getCornersAndCenterPositions(mLauncher);
StringBuilder sb = new StringBuilder();
@@ -162,12 +163,10 @@
mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
- if (!TestStabilityRule.isPresubmit()) { // b/315847371
- Log.d(UIOBJECT_STALE_ELEMENT, "second getWorkspaceIconsPositions()");
- Map<String, Point> finalPositions =
- mLauncher.getWorkspace().getWorkspaceIconsPositions();
- assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
- }
+ Log.d(UIOBJECT_STALE_ELEMENT, "second getWorkspaceIconsPositions()");
+ Map<String, Point> finalPositions =
+ mLauncher.getWorkspace().getWorkspaceIconsPositions();
+ assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
} finally {
TestUtil.uninstallDummyApp();
}
@@ -178,6 +177,8 @@
*/
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/338869019
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/338869019
public void testAddDeleteShortcutOnHotseat() {
mLauncher.getWorkspace()
.deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
diff --git a/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java b/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java
index 715a1f8..7242e9c 100644
--- a/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java
+++ b/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java
@@ -30,6 +30,7 @@
import android.view.animation.PathInterpolator;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.launcher3.CellLayout;
@@ -50,7 +51,8 @@
@Mock
CellLayout mCellLayout;
- private final PreviewBackground mPreviewBackground = new PreviewBackground();
+ private final PreviewBackground mPreviewBackground =
+ new PreviewBackground(InstrumentationRegistry.getInstrumentation().getContext());
@Before
public void setUp() {
diff --git a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 4ec5b0e..da14425 100644
--- a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -28,7 +28,8 @@
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.FastBitmapDrawable
import com.android.launcher3.icons.UserBadgeDrawable
-import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.util.FlagOp
import com.android.launcher3.util.LauncherLayoutBuilder
@@ -46,7 +47,7 @@
private lateinit var previewItemManager: PreviewItemManager
private lateinit var context: Context
- private lateinit var folderItems: ArrayList<WorkspaceItemInfo>
+ private lateinit var folderItems: ArrayList<ItemInfo>
private lateinit var modelHelper: LauncherModelHelper
private lateinit var folderIcon: FolderIcon
@@ -71,15 +72,17 @@
.build()
)
.loadModelSync()
- folderItems = modelHelper.bgDataModel.folders.valueAt(0).contents
- folderIcon.mInfo = modelHelper.bgDataModel.folders.valueAt(0)
- folderIcon.mInfo.contents = folderItems
+ folderItems = modelHelper.bgDataModel.collections.valueAt(0).getContents()
+ folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo
+ folderIcon.mInfo.getContents().addAll(folderItems)
+ // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
+ val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
// Set first icon to be themed.
- folderItems[0]
+ folderApps[0]
.bitmap
.setMonoIcon(
- folderItems[0].bitmap.icon,
+ folderApps[0].bitmap.icon,
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
@@ -88,7 +91,7 @@
)
// Set second icon to be non-themed.
- folderItems[1]
+ folderApps[1]
.bitmap
.setMonoIcon(
null,
@@ -100,23 +103,21 @@
)
// Set third icon to be themed with badge.
- folderItems[2]
+ folderApps[2]
.bitmap
.setMonoIcon(
- folderItems[2].bitmap.icon,
+ folderApps[2].bitmap.icon,
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
previewItemManager.mIconSize
)
)
- folderItems[2].bitmap =
- folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
+ folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
// Set fourth icon to be non-themed with badge.
- folderItems[3].bitmap =
- folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
- folderItems[3]
+ folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
+ folderApps[3]
.bitmap
.setMonoIcon(
null,
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
index 78c61d5..370af0c 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -35,7 +35,7 @@
import org.mockito.kotlin.mock
import org.mockito.kotlin.same
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
/** Tests for [AddWorkspaceItemsTask] */
@@ -97,7 +97,8 @@
val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
assertThat(addedItems.size).isEqualTo(0)
- verifyZeroInteractions(mWorkspaceItemSpaceFinder)
+ // b/343530737
+ verifyNoMoreInteractions(mWorkspaceItemSpaceFinder)
}
@Test
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 7a0b60a..328558d 100644
--- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -8,8 +8,6 @@
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3;
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -98,7 +96,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325283522
public void testCacheUpdate_update_apps() {
// Run on model executor so that no other task runs in the middle.
runOnExecutorSync(MODEL_EXECUTOR, () -> {
@@ -129,7 +126,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325283522
public void testSessionUpdate_updates_pending_apps() {
// Run on model executor so that no other task runs in the middle.
runOnExecutorSync(MODEL_EXECUTOR, () -> {
@@ -160,6 +156,6 @@
}
private List<WorkspaceItemInfo> allItems() {
- return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).contents;
+ return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).getAppContents();
}
}
diff --git a/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 2b89321..10785f7 100644
--- a/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -87,7 +87,7 @@
assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
- assertEquals(3, ((FolderInfo) info).contents.size());
+ assertEquals(3, ((FolderInfo) info).getContents().size());
}
@Test
@@ -102,7 +102,7 @@
assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
- assertEquals(3, ((FolderInfo) info).contents.size());
+ assertEquals(3, ((FolderInfo) info).getContents().size());
assertEquals("CustomFolder", info.title.toString());
}
@@ -154,11 +154,11 @@
// Verify folder
assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
FolderInfo info = (FolderInfo) mModelHelper.getBgDataModel().workspaceItems.get(0);
- assertEquals(3, info.contents.size());
+ assertEquals(3, info.getContents().size());
// Verify last icon
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT,
- info.contents.get(info.contents.size() - 1).itemType);
+ info.getContents().get(info.getContents().size() - 1).itemType);
}
private void writeLayoutAndLoad(LauncherLayoutBuilder builder) throws Exception {
diff --git a/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt b/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
new file mode 100644
index 0000000..aadf72e
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageInstaller.SessionInfo
+import android.os.UserHandle
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.model.FirstScreenBroadcastHelper.MAX_BROADCAST_SIZE
+import com.android.launcher3.model.FirstScreenBroadcastHelper.getTotalItemCount
+import com.android.launcher3.model.FirstScreenBroadcastHelper.truncateModelForBroadcast
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.PackageManagerHelper
+import com.android.launcher3.util.PackageUserKey
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+class FirstScreenBroadcastHelperTest {
+ private val context = spy(InstrumentationRegistry.getInstrumentation().targetContext)
+ private val mockPmHelper = mock<PackageManagerHelper>()
+ private val expectedAppPackage = "appPackageExpected"
+ private val expectedComponentName = ComponentName(expectedAppPackage, "expectedClass")
+ private val expectedInstallerPackage = "installerPackage"
+ private val expectedIntent =
+ Intent().apply {
+ component = expectedComponentName
+ setPackage(expectedAppPackage)
+ }
+ private val unexpectedAppPackage = "appPackageUnexpected"
+ private val unexpectedComponentName = ComponentName(expectedAppPackage, "unexpectedClass")
+ private val firstScreenItems =
+ listOf(
+ WorkspaceItemInfo().apply {
+ container = CONTAINER_DESKTOP
+ intent = expectedIntent
+ },
+ WorkspaceItemInfo().apply {
+ container = CONTAINER_HOTSEAT
+ intent = expectedIntent
+ },
+ LauncherAppWidgetInfo().apply { providerName = expectedComponentName }
+ )
+
+ @Test
+ fun `Broadcast Models are created with Pending Items from first screen`() {
+ // Given
+ val sessionInfoExpected =
+ SessionInfo().apply {
+ installerPackageName = expectedInstallerPackage
+ appPackageName = expectedAppPackage
+ }
+ val sessionInfoUnexpected =
+ SessionInfo().apply {
+ installerPackageName = expectedInstallerPackage
+ appPackageName = unexpectedAppPackage
+ }
+ val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
+ hashMapOf(
+ PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
+ PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected
+ )
+
+ // When
+ val actualResult =
+ FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+ packageManagerHelper = mockPmHelper,
+ firstScreenItems = firstScreenItems,
+ userKeyToSessionMap = sessionInfoMap,
+ allWidgets = listOf()
+ )
+
+ // Then
+ val expectedResult =
+ listOf(
+ FirstScreenBroadcastModel(
+ installerPackage = expectedInstallerPackage,
+ pendingWorkspaceItems = mutableSetOf(expectedAppPackage),
+ pendingHotseatItems = mutableSetOf(expectedAppPackage),
+ pendingWidgetItems = mutableSetOf(expectedAppPackage)
+ )
+ )
+
+ assertEquals(expectedResult, actualResult)
+ }
+
+ @Test
+ fun `Broadcast Models are created with Installed Items from first screen`() {
+ // Given
+ whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage))
+ .thenReturn(expectedInstallerPackage)
+
+ // When
+ val actualResult =
+ FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+ packageManagerHelper = mockPmHelper,
+ firstScreenItems = firstScreenItems,
+ userKeyToSessionMap = hashMapOf(),
+ allWidgets =
+ listOf(
+ LauncherAppWidgetInfo().apply {
+ providerName = expectedComponentName
+ screenId = 0
+ }
+ )
+ )
+
+ // Then
+ val expectedResult =
+ listOf(
+ FirstScreenBroadcastModel(
+ installerPackage = expectedInstallerPackage,
+ installedHotseatItems = mutableSetOf(expectedAppPackage),
+ installedWorkspaceItems = mutableSetOf(expectedAppPackage),
+ firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage)
+ )
+ )
+ assertEquals(expectedResult, actualResult)
+ }
+
+ @Test
+ fun `Broadcast Models are created with Installed Widgets from every screen`() {
+ // Given
+ val expectedAppPackage2 = "appPackageExpected2"
+ val expectedComponentName2 = ComponentName(expectedAppPackage2, "expectedClass2")
+ whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage))
+ .thenReturn(expectedInstallerPackage)
+ whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage2))
+ .thenReturn(expectedInstallerPackage)
+
+ // When
+ val actualResult =
+ FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+ packageManagerHelper = mockPmHelper,
+ firstScreenItems = listOf(),
+ userKeyToSessionMap = hashMapOf(),
+ allWidgets =
+ listOf(
+ LauncherAppWidgetInfo().apply {
+ providerName = expectedComponentName
+ screenId = 0
+ },
+ LauncherAppWidgetInfo().apply {
+ providerName = expectedComponentName2
+ screenId = 1
+ },
+ LauncherAppWidgetInfo().apply {
+ providerName = unexpectedComponentName
+ screenId = 0
+ }
+ )
+ )
+
+ // Then
+ val expectedResult =
+ listOf(
+ FirstScreenBroadcastModel(
+ installerPackage = expectedInstallerPackage,
+ installedHotseatItems = mutableSetOf(),
+ installedWorkspaceItems = mutableSetOf(),
+ firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage),
+ secondaryScreenInstalledWidgets = mutableSetOf(expectedAppPackage2)
+ )
+ )
+ assertEquals(expectedResult, actualResult)
+ }
+
+ @Test
+ fun `Broadcast Models are created with Pending Items in Collections from the first screen`() {
+ // Given
+ val sessionInfoExpected =
+ SessionInfo().apply {
+ installerPackageName = expectedInstallerPackage
+ appPackageName = expectedAppPackage
+ }
+ val sessionInfoUnexpected =
+ SessionInfo().apply {
+ installerPackageName = expectedInstallerPackage
+ appPackageName = unexpectedAppPackage
+ }
+ val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
+ hashMapOf(
+ PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
+ PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected,
+ )
+ val expectedItemInfo = WorkspaceItemInfo().apply { intent = expectedIntent }
+ val expectedFolderInfo = FolderInfo().apply { add(expectedItemInfo) }
+ val firstScreenItems = listOf(expectedFolderInfo)
+
+ // When
+ val actualResult =
+ FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+ packageManagerHelper = mockPmHelper,
+ firstScreenItems = firstScreenItems,
+ userKeyToSessionMap = sessionInfoMap,
+ allWidgets = listOf()
+ )
+
+ // Then
+ val expectedResult =
+ listOf(
+ FirstScreenBroadcastModel(
+ installerPackage = expectedInstallerPackage,
+ pendingCollectionItems = mutableSetOf(expectedAppPackage)
+ )
+ )
+ assertEquals(expectedResult, actualResult)
+ }
+
+ @Test
+ fun `Models with too many items get truncated to max Broadcast size`() {
+ // given
+ val broadcastModel =
+ FirstScreenBroadcastModel(
+ installerPackage = expectedInstallerPackage,
+ pendingCollectionItems =
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+ pendingWorkspaceItems =
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+ pendingHotseatItems =
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+ pendingWidgetItems =
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+ installedWorkspaceItems =
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+ installedHotseatItems =
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+ firstScreenInstalledWidgets =
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+ secondaryScreenInstalledWidgets =
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }
+ )
+
+ // When
+ broadcastModel.truncateModelForBroadcast()
+
+ // Then
+ assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount())
+ }
+
+ @Test
+ fun `Broadcast truncates installed Hotseat items before other installed items`() {
+ // Given
+ val broadcastModel =
+ FirstScreenBroadcastModel(
+ installerPackage = expectedInstallerPackage,
+ installedWorkspaceItems =
+ mutableSetOf<String>().apply { repeat(50) { add(it.toString()) } },
+ firstScreenInstalledWidgets =
+ mutableSetOf<String>().apply { repeat(10) { add(it.toString()) } },
+ secondaryScreenInstalledWidgets =
+ mutableSetOf<String>().apply { repeat(10) { add((it + 10).toString()) } },
+ installedHotseatItems =
+ mutableSetOf<String>().apply { repeat(10) { add(it.toString()) } },
+ )
+
+ // When
+ broadcastModel.truncateModelForBroadcast()
+
+ // Then
+ assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount())
+ assertEquals(50, broadcastModel.installedWorkspaceItems.size)
+ assertEquals(10, broadcastModel.firstScreenInstalledWidgets.size)
+ assertEquals(10, broadcastModel.secondaryScreenInstalledWidgets.size)
+ assertEquals(0, broadcastModel.installedHotseatItems.size)
+ }
+
+ @Test
+ fun `Broadcast truncates Widgets before the rest of the first screen items`() {
+ // Given
+ val broadcastModel =
+ FirstScreenBroadcastModel(
+ installerPackage = expectedInstallerPackage,
+ installedWorkspaceItems =
+ mutableSetOf<String>().apply { repeat(70) { add(it.toString()) } },
+ firstScreenInstalledWidgets =
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+ secondaryScreenInstalledWidgets =
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+ )
+
+ // When
+ broadcastModel.truncateModelForBroadcast()
+
+ // Then
+ assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount())
+ assertEquals(70, broadcastModel.installedWorkspaceItems.size)
+ assertEquals(0, broadcastModel.firstScreenInstalledWidgets.size)
+ assertEquals(0, broadcastModel.secondaryScreenInstalledWidgets.size)
+ }
+
+ @Test
+ fun `Broadcasts are correctly formed with Extras for each Installer`() {
+ // Given
+ val broadcastModels: List<FirstScreenBroadcastModel> =
+ listOf(
+ FirstScreenBroadcastModel(
+ installerPackage = expectedInstallerPackage,
+ pendingCollectionItems = mutableSetOf("pendingCollectionItem"),
+ pendingWorkspaceItems = mutableSetOf("pendingWorkspaceItem"),
+ pendingHotseatItems = mutableSetOf("pendingHotseatItems"),
+ pendingWidgetItems = mutableSetOf("pendingWidgetItems"),
+ installedWorkspaceItems = mutableSetOf("installedWorkspaceItems"),
+ installedHotseatItems = mutableSetOf("installedHotseatItems"),
+ firstScreenInstalledWidgets = mutableSetOf("firstScreenInstalledWidgetItems"),
+ secondaryScreenInstalledWidgets = mutableSetOf("secondaryInstalledWidgetItems")
+ )
+ )
+ val expectedPendingIntent =
+ PendingIntent.getActivity(
+ context,
+ 0 /* requestCode */,
+ Intent(),
+ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ // When
+ FirstScreenBroadcastHelper.sendBroadcastsForModels(context, broadcastModels)
+
+ // Then
+ val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(context).sendBroadcast(argumentCaptor.capture())
+
+ assertEquals(
+ "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS",
+ argumentCaptor.value.action
+ )
+ assertEquals(expectedInstallerPackage, argumentCaptor.value.`package`)
+ assertEquals(
+ expectedPendingIntent,
+ argumentCaptor.value.getParcelableExtra("verificationToken")
+ )
+ assertEquals(
+ arrayListOf("pendingCollectionItem"),
+ argumentCaptor.value.getStringArrayListExtra("folderItem")
+ )
+ assertEquals(
+ arrayListOf("pendingWorkspaceItem"),
+ argumentCaptor.value.getStringArrayListExtra("workspaceItem")
+ )
+ assertEquals(
+ arrayListOf("pendingHotseatItems"),
+ argumentCaptor.value.getStringArrayListExtra("hotseatItem")
+ )
+ assertEquals(
+ arrayListOf("pendingWidgetItems"),
+ argumentCaptor.value.getStringArrayListExtra("widgetItem")
+ )
+ assertEquals(
+ arrayListOf("installedWorkspaceItems"),
+ argumentCaptor.value.getStringArrayListExtra("workspaceInstalledItems")
+ )
+ assertEquals(
+ arrayListOf("installedHotseatItems"),
+ argumentCaptor.value.getStringArrayListExtra("hotseatInstalledItems")
+ )
+ assertEquals(
+ arrayListOf("firstScreenInstalledWidgetItems", "secondaryInstalledWidgetItems"),
+ argumentCaptor.value.getStringArrayListExtra("widgetInstalledItems")
+ )
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index 2118ed6..c4a4c9b 100644
--- a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -24,13 +24,11 @@
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.LauncherModelHelper.*
import com.android.launcher3.util.TestUtil
-import com.android.launcher3.util.rule.TestStabilityRule
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import java.util.concurrent.CountDownLatch
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -58,8 +56,6 @@
TEST_ACTIVITY14
)
- @get:Rule(order = 0) val testStabilityRule = TestStabilityRule()
-
@Before
fun setUp() {
modelHelper = LauncherModelHelper()
@@ -91,9 +87,6 @@
@Test
@Throws(Exception::class)
- @TestStabilityRule.Stability(
- flavors = TestStabilityRule.LOCAL or TestStabilityRule.PLATFORM_POSTSUBMIT
- ) // b/319923578
fun folderLoadedWithHighRes_max_3x3() {
val idp = LauncherAppState.getIDP(modelHelper.sandboxContext)
idp.numFolderColumns = intArrayOf(3, 3, 3, 3)
@@ -107,9 +100,6 @@
@Test
@Throws(Exception::class)
- @TestStabilityRule.Stability(
- flavors = TestStabilityRule.LOCAL or TestStabilityRule.PLATFORM_POSTSUBMIT
- ) // b/319923578
fun folderLoadedWithHighRes_max_4x4() {
val idp = LauncherAppState.getIDP(modelHelper.sandboxContext)
idp.numFolderColumns = intArrayOf(4, 4, 4, 4)
@@ -123,10 +113,6 @@
@Test
@Throws(Exception::class)
- // Stress tests are long. We permanently demote them from presubmit to match the presubmit SLO.
- @TestStabilityRule.Stability(
- flavors = TestStabilityRule.LOCAL or TestStabilityRule.PLATFORM_POSTSUBMIT
- ) // b/319923578
fun folderLoadedWithHighRes_differentFolderConfigurations() {
val idp = LauncherAppState.getIDP(modelHelper.sandboxContext)
idp.numFolderColumns = intArrayOf(4, 3, 4, 4)
@@ -165,11 +151,11 @@
// Reload again with correct icon state
app.model.forceReload()
modelHelper.loadModelSync()
- val folders = modelHelper.getBgDataModel().folders
+ val collections = modelHelper.getBgDataModel().collections
- assertThat(folders.size()).isEqualTo(1)
- assertThat(folders.valueAt(0).contents.size).isEqualTo(itemCount)
- return folders.valueAt(0).contents
+ assertThat(collections.size()).isEqualTo(1)
+ assertThat(collections.valueAt(0).getAppContents().size).isEqualTo(itemCount)
+ return collections.valueAt(0).getAppContents()
}
private fun verifyHighRes(items: ArrayList<WorkspaceItemInfo>, vararg indices: Int) {
diff --git a/tests/src/com/android/launcher3/model/GridMigrationTest.kt b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
new file mode 100644
index 0000000..15222a4
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Flags
+import com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE
+import com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME
+import com.android.launcher3.celllayout.board.CellLayoutBoard
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.rule.TestToPhoneFileCopier
+import com.android.launcher3.util.rule.setFlags
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private val phoneContext = InstrumentationRegistry.getInstrumentation().targetContext
+
+data class EntryData(val x: Int, val y: Int, val spanX: Int, val spanY: Int, val rank: Int)
+
+/**
+ * Holds the data needed to run a test in GridMigrationTest, usually we would have a src
+ * GridMigrationData and a dst GridMigrationData meaning the data after a migration has occurred.
+ * This class holds a gridState, which is the size of the grid like 5x5 (among other things). a
+ * dbHelper which contains the readable database and writable database used to migrate the
+ * databases.
+ *
+ * You can also get all the entries defined in the dbHelper database.
+ */
+class GridMigrationData(dbFileName: String?, val gridState: DeviceGridState) {
+
+ val dbHelper: DatabaseHelper =
+ DatabaseHelper(
+ phoneContext,
+ dbFileName,
+ { UserCache.INSTANCE.get(phoneContext).getSerialNumberForUser(it) },
+ {}
+ )
+
+ fun readEntries(): List<GridSizeMigrationUtil.DbEntry> =
+ GridSizeMigrationUtil.readAllEntries(dbHelper.readableDatabase, TABLE_NAME, phoneContext)
+}
+
+/**
+ * Test the migration of a database from one size to another. It reads a database from the test
+ * assets, uploads it into the phone and migrates the database to a database in memory which is
+ * later compared against a database in the test assets to make sure they are identical.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GridMigrationTest {
+ private val DB_FILE = "test_launcher.db"
+
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+ // Copying the src db for all tests.
+ @JvmField
+ @Rule
+ val fileCopier =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/$DB_FILE",
+ dest = "databases/$DB_FILE",
+ removeOnFinish = true
+ )
+
+ @Before
+ fun setup() {
+ setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX)
+ }
+
+ private fun migrate(src: GridMigrationData, dst: GridMigrationData) {
+ GridSizeMigrationUtil.migrateGridIfNeeded(
+ phoneContext,
+ src.gridState,
+ dst.gridState,
+ dst.dbHelper,
+ src.dbHelper.readableDatabase
+ )
+ }
+
+ /**
+ * Makes sure that none of the items overlaps on the result, i.e. no widget or icons share the
+ * same space in the db.
+ */
+ private fun validateDb(data: GridMigrationData) {
+ // The array size is just a big enough number to fit all the number of workspaces
+ val boards = Array(100) { CellLayoutBoard(data.gridState.columns, data.gridState.rows) }
+ data.readEntries().forEach {
+ val cellLayoutBoard = boards[it.screenId]
+ assert(cellLayoutBoard.isEmpty(it.cellX, it.cellY, it.spanX, it.spanY)) {
+ "Db has overlapping items"
+ }
+ cellLayoutBoard.addWidget(it.cellX, it.cellY, it.spanX, it.spanY)
+ }
+ }
+
+ private fun compare(dst: GridMigrationData, target: GridMigrationData) {
+ val sort = compareBy<GridSizeMigrationUtil.DbEntry>({ it.cellX }, { it.cellY })
+ val mapF = { it: GridSizeMigrationUtil.DbEntry ->
+ EntryData(it.cellX, it.cellY, it.spanX, it.spanY, it.rank)
+ }
+ val entriesDst = dst.readEntries().sortedWith(sort).map(mapF)
+ val entriesTarget = target.readEntries().sortedWith(sort).map(mapF)
+
+ assert(entriesDst == entriesTarget) {
+ "The elements on the dst database is not the same as in the target"
+ }
+ }
+
+ /**
+ * Migrate src into dst and compare to target. This method validates 3 things:
+ * 1. dst has the same number of items as src after the migration, meaning, none of the items
+ * were removed during the migration.
+ * 2. dst is valid, meaning that none of the items overlap with each other.
+ * 3. dst is equal to target to ensure we don't unintentionally change the migration logic.
+ */
+ private fun runTest(src: GridMigrationData, dst: GridMigrationData, target: GridMigrationData) {
+ migrate(src, dst)
+ assert(src.readEntries().size == dst.readEntries().size) {
+ "Source db and destination db do not contain the same number of elements"
+ }
+ validateDb(dst)
+ compare(dst, target)
+ }
+
+ @JvmField
+ @Rule
+ val result5x5to3x3 =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/result5x5to3x3.db",
+ dest = "databases/result5x5to3x3.db",
+ removeOnFinish = true
+ )
+
+ @Test
+ fun `5x5 to 3x3`() =
+ 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(3, 3, 3, TYPE_PHONE, "")
+ ),
+ target =
+ GridMigrationData("result5x5to3x3.db", DeviceGridState(3, 3, 3, TYPE_PHONE, ""))
+ )
+
+ @JvmField
+ @Rule
+ val result5x5to4x7 =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/result5x5to4x7.db",
+ dest = "databases/result5x5to4x7.db",
+ removeOnFinish = true
+ )
+
+ @Test
+ fun `5x5 to 4x7`() =
+ 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(4, 7, 4, TYPE_PHONE, "")
+ ),
+ target =
+ GridMigrationData("result5x5to4x7.db", DeviceGridState(4, 7, 4, TYPE_PHONE, ""))
+ )
+
+ @JvmField
+ @Rule
+ val result5x5to5x8 =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/result5x5to5x8.db",
+ dest = "databases/result5x5to5x8.db",
+ removeOnFinish = true
+ )
+
+ @Test
+ fun `5x5 to 5x8`() =
+ 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("result5x5to5x8.db", DeviceGridState(5, 8, 5, TYPE_PHONE, ""))
+ )
+
+ @JvmField
+ @Rule
+ val flaggedResult5x5to5x8 =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/flagged_result5x5to5x8.db",
+ dest = "databases/flagged_result5x5to5x8.db",
+ removeOnFinish = true
+ )
+
+ @Test
+ fun `flagged 5x5 to 5x8`() {
+ setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX)
+ 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(
+ "flagged_result5x5to5x8.db",
+ DeviceGridState(5, 8, 5, TYPE_PHONE, "")
+ )
+ )
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 56ac960..b4945d7 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -79,6 +79,7 @@
private LauncherModelHelper mModelHelper;
private LauncherAppState mApp;
+ private PackageManagerHelper mPmHelper;
private MatrixCursor mCursor;
private InvariantDeviceProfile mIDP;
@@ -92,6 +93,7 @@
mContext = mModelHelper.sandboxContext;
mIDP = InvariantDeviceProfile.INSTANCE.get(mContext);
mApp = LauncherAppState.getInstance(mContext);
+ mPmHelper = PackageManagerHelper.INSTANCE.get(mContext);
mCursor = new MatrixCursor(new String[] {
ICON, TITLE, _ID, CONTAINER, ITEM_TYPE,
@@ -101,7 +103,7 @@
});
UserManagerState ums = new UserManagerState();
- mLoaderCursor = new LoaderCursor(mCursor, mApp, ums, null);
+ mLoaderCursor = new LoaderCursor(mCursor, mApp, ums, mPmHelper, null);
ums.allUsers.put(0, Process.myUserHandle());
}
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index 439a4dc..d16674c 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -1,10 +1,13 @@
package com.android.launcher3.model
import android.appwidget.AppWidgetManager
+import android.content.Intent
import android.os.UserHandle
import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.launcher3.Flags
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
@@ -14,6 +17,7 @@
import com.android.launcher3.icons.cache.CachingLogic
import com.android.launcher3.icons.cache.IconCacheUpdateHandler
import com.android.launcher3.pm.UserCache
+import com.android.launcher3.provider.RestoreDbTask
import com.android.launcher3.ui.TestViewHelpers
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
@@ -21,21 +25,30 @@
import com.android.launcher3.util.UserIconInfo
import com.google.common.truth.Truth
import java.util.concurrent.CountDownLatch
+import junit.framework.Assert.assertEquals
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.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyList
+import org.mockito.ArgumentMatchers.anyMap
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import org.mockito.MockitoSession
import org.mockito.Spy
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql"
@@ -43,10 +56,24 @@
@RunWith(AndroidJUnit4::class)
class LoaderTaskTest {
private var context = SandboxModelContext()
+ private val expectedBroadcastModel =
+ FirstScreenBroadcastModel(
+ installerPackage = "installerPackage",
+ pendingCollectionItems = mutableSetOf("pendingCollectionItem"),
+ pendingWidgetItems = mutableSetOf("pendingWidgetItem"),
+ pendingHotseatItems = mutableSetOf("pendingHotseatItem"),
+ pendingWorkspaceItems = mutableSetOf("pendingWorkspaceItem"),
+ installedHotseatItems = mutableSetOf("installedHotseatItem"),
+ installedWorkspaceItems = mutableSetOf("installedWorkspaceItem"),
+ firstScreenInstalledWidgets = mutableSetOf("installedFirstScreenWidget"),
+ secondaryScreenInstalledWidgets = mutableSetOf("installedSecondaryScreenWidget")
+ )
+ private lateinit var mockitoSession: MockitoSession
+
@Mock private lateinit var app: LauncherAppState
@Mock private lateinit var bgAllAppsList: AllAppsList
@Mock private lateinit var modelDelegate: ModelDelegate
- @Mock private lateinit var launcherBinder: LauncherBinder
+ @Mock private lateinit var launcherBinder: BaseLauncherBinder
@Mock private lateinit var launcherModel: LauncherModel
@Mock private lateinit var transaction: LoaderTransaction
@Mock private lateinit var iconCache: IconCache
@@ -61,7 +88,11 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
-
+ mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(FirstScreenBroadcastHelper::class.java)
+ .startMocking()
val idp =
InvariantDeviceProfile().apply {
numRows = 5
@@ -90,6 +121,7 @@
@After
fun tearDown() {
context.onDestroy()
+ mockitoSession.finishMocking()
}
@Test
@@ -103,7 +135,7 @@
.runSyncOnBackgroundThread()
Truth.assertThat(workspaceItems.size).isAtLeast(25)
Truth.assertThat(appWidgets.size).isAtLeast(7)
- Truth.assertThat(folders.size()).isAtLeast(8)
+ Truth.assertThat(collections.size()).isAtLeast(8)
Truth.assertThat(itemsIdMap.size()).isAtLeast(40)
}
@@ -166,6 +198,141 @@
verify(bgAllAppsList, Mockito.never())
.setFlags(BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED, true)
}
+
+ @Test
+ fun `When launcher_broadcast_installed_apps and is restore then send installed item broadcast`() {
+ // Given
+ val spyContext = spy(context)
+ `when`(app.context).thenReturn(spyContext)
+ whenever(
+ FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+ anyOrNull(),
+ anyList(),
+ anyMap(),
+ anyList()
+ )
+ )
+ .thenReturn(listOf(expectedBroadcastModel))
+
+ whenever(
+ FirstScreenBroadcastHelper.sendBroadcastsForModels(
+ spyContext,
+ listOf(expectedBroadcastModel)
+ )
+ )
+ .thenCallRealMethod()
+
+ Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1)
+ RestoreDbTask.setPending(spyContext)
+
+ // When
+ LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+ .runSyncOnBackgroundThread()
+
+ // Then
+ val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(spyContext).sendBroadcast(argumentCaptor.capture())
+ val actualBroadcastIntent = argumentCaptor.value
+ assertEquals(expectedBroadcastModel.installerPackage, actualBroadcastIntent.`package`)
+ assertEquals(
+ ArrayList(expectedBroadcastModel.installedWorkspaceItems),
+ actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems")
+ )
+ assertEquals(
+ ArrayList(expectedBroadcastModel.installedHotseatItems),
+ actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems")
+ )
+ assertEquals(
+ ArrayList(
+ expectedBroadcastModel.firstScreenInstalledWidgets +
+ expectedBroadcastModel.secondaryScreenInstalledWidgets
+ ),
+ actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems")
+ )
+ assertEquals(
+ ArrayList(expectedBroadcastModel.pendingCollectionItems),
+ actualBroadcastIntent.getStringArrayListExtra("folderItem")
+ )
+ assertEquals(
+ ArrayList(expectedBroadcastModel.pendingWorkspaceItems),
+ actualBroadcastIntent.getStringArrayListExtra("workspaceItem")
+ )
+ assertEquals(
+ ArrayList(expectedBroadcastModel.pendingHotseatItems),
+ actualBroadcastIntent.getStringArrayListExtra("hotseatItem")
+ )
+ assertEquals(
+ ArrayList(expectedBroadcastModel.pendingWidgetItems),
+ actualBroadcastIntent.getStringArrayListExtra("widgetItem")
+ )
+ }
+
+ @Test
+ fun `When not a restore then installed item broadcast not sent`() {
+ // Given
+ val spyContext = spy(context)
+ `when`(app.context).thenReturn(spyContext)
+ whenever(
+ FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+ anyOrNull(),
+ anyList(),
+ anyMap(),
+ anyList()
+ )
+ )
+ .thenReturn(listOf(expectedBroadcastModel))
+
+ whenever(
+ FirstScreenBroadcastHelper.sendBroadcastsForModels(
+ spyContext,
+ listOf(expectedBroadcastModel)
+ )
+ )
+ .thenCallRealMethod()
+
+ Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1)
+
+ // When
+ LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+ .runSyncOnBackgroundThread()
+
+ // Then
+ verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java))
+ }
+
+ @Test
+ fun `When launcher_broadcast_installed_apps false then installed item broadcast not sent`() {
+ // Given
+ val spyContext = spy(context)
+ `when`(app.context).thenReturn(spyContext)
+ whenever(
+ FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+ anyOrNull(),
+ anyList(),
+ anyMap(),
+ anyList()
+ )
+ )
+ .thenReturn(listOf(expectedBroadcastModel))
+
+ whenever(
+ FirstScreenBroadcastHelper.sendBroadcastsForModels(
+ spyContext,
+ listOf(expectedBroadcastModel)
+ )
+ )
+ .thenCallRealMethod()
+
+ Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0)
+ RestoreDbTask.setPending(spyContext)
+
+ // When
+ LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+ .runSyncOnBackgroundThread()
+
+ // Then
+ verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java))
+ }
}
private fun LoaderTask.runSyncOnBackgroundThread() {
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index 30957fc..6cf3b19 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -23,29 +23,46 @@
import android.content.pm.LauncherApps
import android.content.pm.PackageInstaller
import android.content.pm.ShortcutInfo
+import android.os.Process
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import android.util.LongSparseArray
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.EMPTY_PERSON_ARRAY
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_INFO
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_WIDGET_PROVIDER
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.PROFILE_DELETED
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.IconRequestInfo
import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
+import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY
import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.UserCache
import com.android.launcher3.shortcuts.ShortcutKey
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.UserIconInfo
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
import com.android.launcher3.widget.WidgetInflater
+import com.android.launcher3.widget.WidgetSections
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.RETURNS_DEEP_STUBS
import org.mockito.Mockito.mock
@@ -54,8 +71,10 @@
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
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
class WorkspaceItemProcessorTest {
@@ -64,37 +83,40 @@
@Mock private lateinit var mockBgDataModel: BgDataModel
@Mock private lateinit var mockContext: Context
@Mock private lateinit var mockAppState: LauncherAppState
- @Mock private lateinit var mockIntent: Intent
@Mock private lateinit var mockPmHelper: PackageManagerHelper
@Mock private lateinit var mockLauncherApps: LauncherApps
@Mock private lateinit var mockCursor: LoaderCursor
+ @Mock private lateinit var mockUserCache: UserCache
@Mock private lateinit var mockUserManagerState: UserManagerState
@Mock private lateinit var mockWidgetInflater: WidgetInflater
- private lateinit var userHandle: UserHandle
- private lateinit var iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>>
- private lateinit var componentName: ComponentName
- private lateinit var unlockedUsersArray: LongSparseArray<Boolean>
- private lateinit var keyToPinnedShortcutsMap: MutableMap<ShortcutKey, ShortcutInfo>
- private lateinit var installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo>
- private lateinit var allDeepShortcuts: MutableList<ShortcutInfo>
+ private var intent: Intent = Intent()
+ private var mUserHandle: UserHandle = UserHandle(0)
+ private var mIconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf()
+ private var mComponentName: ComponentName = ComponentName("package", "class")
+ private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray()
+ private var mKeyToPinnedShortcutsMap: MutableMap<ShortcutKey, ShortcutInfo> = mutableMapOf()
+ private var mInstallingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf()
+ private var mAllDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+ private var mWidgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> =
+ mutableMapOf()
+ private var mPendingPackages: MutableSet<PackageUserKey> = mutableSetOf()
private lateinit var itemProcessorUnderTest: WorkspaceItemProcessor
@Before
fun setup() {
- userHandle = UserHandle(0)
+ mUserHandle = UserHandle(0)
mockIconRequestInfo = mock<IconRequestInfo<WorkspaceItemInfo>>()
- iconRequestInfos = mutableListOf(mockIconRequestInfo)
mockWorkspaceInfo = mock<WorkspaceItemInfo>()
mockBgDataModel = mock<BgDataModel>()
- componentName = ComponentName("package", "class")
- unlockedUsersArray = LongSparseArray<Boolean>(1).apply { put(101, true) }
- mockIntent =
- mock<Intent>().apply {
- whenever(component).thenReturn(componentName)
- whenever(`package`).thenReturn("pkg")
- whenever(getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID)).thenReturn("")
+ mComponentName = ComponentName("package", "class")
+ mUnlockedUsersArray = LongSparseArray<Boolean>(1).apply { put(101, true) }
+ intent =
+ Intent().apply {
+ component = mComponentName
+ `package` = "pkg"
+ putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "")
}
mockContext =
mock<Context>().apply {
@@ -109,34 +131,43 @@
}
mockPmHelper =
mock<PackageManagerHelper>().apply {
- whenever(getAppLaunchIntent(componentName.packageName, userHandle))
- .thenReturn(mockIntent)
+ whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
+ .thenReturn(intent)
}
mockLauncherApps =
mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
- whenever(isActivityEnabled(componentName, userHandle)).thenReturn(true)
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true)
}
mockCursor =
mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply {
- user = userHandle
+ user = mUserHandle
itemType = ITEM_TYPE_APPLICATION
id = 1
restoreFlag = 1
serialNumber = 101
- whenever(parseIntent()).thenReturn(mockIntent)
+ whenever(parseIntent()).thenReturn(intent)
whenever(markRestored()).doAnswer { restoreFlag = 0 }
- whenever(updater().put(Favorites.INTENT, mockIntent.toUri(0)).commit())
- .thenReturn(1)
+ whenever(updater().put(Favorites.INTENT, intent.toUri(0)).commit()).thenReturn(1)
whenever(getAppShortcutInfo(any(), any(), any(), any()))
.thenReturn(mockWorkspaceInfo)
whenever(createIconRequestInfo(any(), any())).thenReturn(mockIconRequestInfo)
}
+ mockUserCache =
+ mock<UserCache>().apply {
+ val userIconInfo =
+ mock<UserIconInfo>().apply { whenever(isPrivate).thenReturn(false) }
+ whenever(getUserInfo(any())).thenReturn(userIconInfo)
+ }
+
mockUserManagerState = mock<UserManagerState>()
mockWidgetInflater = mock<WidgetInflater>()
- keyToPinnedShortcutsMap = mutableMapOf()
- installingPkgs = hashMapOf()
- allDeepShortcuts = mutableListOf()
+ mKeyToPinnedShortcutsMap = mutableMapOf()
+ mInstallingPkgs = hashMapOf()
+ mAllDeepShortcuts = mutableListOf()
+ mWidgetProvidersMap = mutableMapOf()
+ mIconRequestInfos = mutableListOf()
+ mPendingPackages = mutableSetOf()
}
/**
@@ -147,24 +178,26 @@
private fun createWorkspaceItemProcessorUnderTest(
cursor: LoaderCursor = mockCursor,
memoryLogger: LoaderMemoryLogger? = null,
+ userCache: UserCache = mockUserCache,
userManagerState: UserManagerState = mockUserManagerState,
launcherApps: LauncherApps = mockLauncherApps,
- shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo> = keyToPinnedShortcutsMap,
+ shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo> = mKeyToPinnedShortcutsMap,
app: LauncherAppState = mockAppState,
bgDataModel: BgDataModel = mockBgDataModel,
- widgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> = mutableMapOf(),
+ widgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> = mWidgetProvidersMap,
widgetInflater: WidgetInflater = mockWidgetInflater,
pmHelper: PackageManagerHelper = mockPmHelper,
- iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf(),
+ iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mIconRequestInfos,
isSdCardReady: Boolean = false,
- pendingPackages: MutableSet<PackageUserKey> = mutableSetOf(),
- unlockedUsers: LongSparseArray<Boolean> = unlockedUsersArray,
- installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf(),
- allDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+ pendingPackages: MutableSet<PackageUserKey> = mPendingPackages,
+ unlockedUsers: LongSparseArray<Boolean> = mUnlockedUsersArray,
+ installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = mInstallingPkgs,
+ allDeepShortcuts: MutableList<ShortcutInfo> = mAllDeepShortcuts
) =
WorkspaceItemProcessor(
c = cursor,
memoryLogger = memoryLogger,
+ userCache = userCache,
userManagerState = userManagerState,
launcherApps = launcherApps,
app = app,
@@ -185,9 +218,11 @@
fun `When user is null then mark item deleted`() {
// Given
mockCursor = mock<LoaderCursor>().apply { id = 1 }
- itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
+
// When
+ itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
itemProcessorUnderTest.processItem()
+
// Then
verify(mockCursor).markDeleted("User has been deleted for item id=1", PROFILE_DELETED)
verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
@@ -197,8 +232,9 @@
fun `When app has null intent then mark deleted`() {
// Given
mockCursor.apply { whenever(parseIntent()).thenReturn(null) }
- itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
+
// When
+ itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
itemProcessorUnderTest.processItem()
// Then
verify(mockCursor).markDeleted("Null intent from db for item id=1", MISSING_INFO)
@@ -209,13 +245,13 @@
fun `When app has null target package then mark deleted`() {
// Given
- mockIntent.apply {
- whenever(component).thenReturn(null)
- whenever(`package`).thenReturn(null)
+ intent.apply {
+ component = null
+ `package` = null
}
- itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
// When
+ itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
itemProcessorUnderTest.processItem()
// Then
@@ -227,12 +263,12 @@
fun `When app has empty String target package then mark deleted`() {
// Given
- componentName = ComponentName("", "")
- whenever(mockIntent.component).thenReturn(componentName)
- whenever(mockCursor.parseIntent()).thenReturn(mockIntent)
- itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
+ mComponentName = ComponentName("", "")
+ intent.component = mComponentName
+ intent.`package` = ""
// When
+ itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
itemProcessorUnderTest.processItem()
// Then
@@ -243,10 +279,8 @@
@Test
fun `When valid app then mark restored`() {
- // Given
- itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
-
// When
+ itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
itemProcessorUnderTest.processItem()
// Then
@@ -255,7 +289,7 @@
.isEqualTo(0)
// currently gets marked restored twice, although markRestore() has check for restoreFlag
verify(mockCursor, times(2)).markRestored()
- assertThat(iconRequestInfos).containsExactly(mockIconRequestInfo)
+ assertThat(mIconRequestInfos).containsExactly(mockIconRequestInfo)
verify(mockCursor).checkAndAddItem(mockWorkspaceInfo, mockBgDataModel, null)
}
@@ -265,25 +299,25 @@
// Given
mockLauncherApps =
mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
- whenever(isActivityEnabled(componentName, userHandle)).thenReturn(false)
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
}
mockPmHelper =
mock<PackageManagerHelper>().apply {
- whenever(getAppLaunchIntent(componentName.packageName, userHandle))
- .thenReturn(mockIntent)
+ whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
+ .thenReturn(intent)
}
- itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
// When
+ itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
itemProcessorUnderTest.processItem()
// Then
assertWithMessage("item restoreFlag should be set to 0")
.that(mockCursor.restoreFlag)
.isEqualTo(0)
- verify(mockCursor.updater().put(Favorites.INTENT, mockIntent.toUri(0))).commit()
- assertThat(iconRequestInfos).containsExactly(mockIconRequestInfo)
+ verify(mockCursor.updater().put(Favorites.INTENT, intent.toUri(0))).commit()
+ assertThat(mIconRequestInfos).containsExactly(mockIconRequestInfo)
verify(mockCursor).checkAndAddItem(mockWorkspaceInfo, mockBgDataModel, null)
}
@@ -293,16 +327,17 @@
// Given
mockLauncherApps =
mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
- whenever(isActivityEnabled(componentName, userHandle)).thenReturn(false)
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
}
mockPmHelper =
mock<PackageManagerHelper>().apply {
- whenever(getAppLaunchIntent(componentName.packageName, userHandle)).thenReturn(null)
+ whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
+ .thenReturn(null)
}
- itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
// When
+ itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
itemProcessorUnderTest.processItem()
// Then
@@ -336,21 +371,21 @@
whenever(disabledReason).thenReturn(0)
whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
}
- val shortcutKey = ShortcutKey.fromIntent(mockIntent, mockCursor.user)
- keyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
- iconRequestInfos = mutableListOf()
- itemProcessorUnderTest =
- createWorkspaceItemProcessorUnderTest(allDeepShortcuts = allDeepShortcuts)
+ val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user)
+ mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
+ mIconRequestInfos = mutableListOf()
// When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
itemProcessorUnderTest.processItem()
// Then
assertWithMessage("item restoreFlag should be set to 0")
.that(mockCursor.restoreFlag)
.isEqualTo(0)
- assertThat(iconRequestInfos).isEmpty()
- assertThat(allDeepShortcuts).containsExactly(expectedShortcutInfo)
+ assertThat(mIconRequestInfos).isEmpty()
+ assertThat(mAllDeepShortcuts).containsExactly(expectedShortcutInfo)
verify(mockCursor).markRestored()
verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
@@ -360,18 +395,18 @@
// Given
mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
- iconRequestInfos = mutableListOf()
- keyToPinnedShortcutsMap = hashMapOf()
- itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
+ mIconRequestInfos = mutableListOf()
+ mKeyToPinnedShortcutsMap = hashMapOf()
// When
+ itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
itemProcessorUnderTest.processItem()
// Then
assertWithMessage("item restoreFlag should be set to 0")
.that(mockCursor.restoreFlag)
.isEqualTo(0)
- assertThat(iconRequestInfos).isEmpty()
+ assertThat(mIconRequestInfos).isEmpty()
verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
verify(mockCursor)
.markDeleted(
@@ -381,6 +416,46 @@
}
@Test
+ fun `When valid Pinned Deep Shortcut with null intent package then use targetPkg`() {
+
+ // Given
+ mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
+ val expectedShortcutInfo =
+ mock<ShortcutInfo>().apply {
+ whenever(id).thenReturn("")
+ whenever(`package`).thenReturn("")
+ whenever(activity).thenReturn(mock())
+ whenever(longLabel).thenReturn("")
+ whenever(isEnabled).thenReturn(true)
+ whenever(disabledMessage).thenReturn("")
+ whenever(disabledReason).thenReturn(0)
+ whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
+ whenever(userHandle).thenReturn(Process.myUserHandle())
+ }
+ mIconRequestInfos = mutableListOf()
+ // Make sure shortcuts map has expected key from expected package
+ intent.`package` = mComponentName.packageName
+ val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user)
+ mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
+ // set intent package back to null to test scenario
+ intent.`package` = null
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ assertWithMessage("item restoreFlag should be set to 0")
+ .that(mockCursor.restoreFlag)
+ .isEqualTo(0)
+ assertThat(mIconRequestInfos).isEmpty()
+ assertThat(mAllDeepShortcuts).containsExactly(expectedShortcutInfo)
+ verify(mockCursor).markRestored()
+ verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
+ }
+
+ @Test
fun `When processing Folder then create FolderInfo and mark restored`() {
val actualFolderInfo = FolderInfo()
mockBgDataModel =
@@ -427,4 +502,346 @@
assertThat(actualFolderInfo.options).isEqualTo(expectedFolderInfo.options)
verify(mockCursor).checkAndAddItem(actualFolderInfo, mockBgDataModel, null)
}
+
+ @Test
+ fun `When valid TYPE_REAL App Widget then add item`() {
+
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ val expectedComponentName =
+ ComponentName.unflattenFromString(expectedProvider)!!.flattenToString()
+ val expectedRestoreStatus = FLAG_UI_NOT_READY
+ val expectedAppWidgetId = 0
+ mockCursor.apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ user = mUserHandle
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(appWidgetId).thenReturn(expectedAppWidgetId)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ whenever(
+ updater()
+ .put(Favorites.APPWIDGET_PROVIDER, expectedComponentName)
+ .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
+ .put(Favorites.RESTORED, expectedRestoreStatus)
+ .commit()
+ )
+ .thenReturn(1)
+ }
+ val expectedWidgetInfo =
+ LauncherAppWidgetInfo().apply {
+ appWidgetId = expectedAppWidgetId
+ providerName = ComponentName.unflattenFromString(expectedProvider)
+ restoreStatus = expectedRestoreStatus
+ }
+ val expectedWidgetProviderInfo =
+ mock<LauncherAppWidgetProviderInfo>().apply {
+ provider = ComponentName.unflattenFromString(expectedProvider)
+ whenever(user).thenReturn(mUserHandle)
+ }
+ val inflationResult =
+ WidgetInflater.InflationResult(
+ type = WidgetInflater.TYPE_REAL,
+ widgetInfo = expectedWidgetProviderInfo
+ )
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ val packageUserKey = PackageUserKey("com.google.android.testApp", mUserHandle)
+ mInstallingPkgs[packageUserKey] = PackageInstaller.SessionInfo()
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
+ verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+ val actualWidgetInfo = widgetInfoCaptor.value
+ with(actualWidgetInfo) {
+ assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
+ assertThat(restoreStatus).isEqualTo(expectedWidgetInfo.restoreStatus)
+ assertThat(targetComponent).isEqualTo(expectedWidgetInfo.targetComponent)
+ assertThat(appWidgetId).isEqualTo(expectedWidgetInfo.appWidgetId)
+ }
+ val expectedComponentKey =
+ ComponentKey(expectedWidgetProviderInfo.provider, expectedWidgetProviderInfo.user)
+ assertThat(mWidgetProvidersMap[expectedComponentKey]).isEqualTo(expectedWidgetProviderInfo)
+ }
+
+ @Test
+ fun `When valid Pending Widget then checkAndAddItem`() {
+
+ // Given
+ mockCursor =
+ mock<LoaderCursor>().apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ id = 1
+ user = UserHandle(1)
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider)
+ .thenReturn("com.google.android.testApp/com.android.testApp.testAppProvider")
+ whenever(appWidgetId).thenReturn(0)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ }
+ val mockProviderInfo =
+ mock<LauncherAppWidgetProviderInfo>().apply {
+ provider = mock()
+ whenever(user).thenReturn(UserHandle(1))
+ }
+ val inflationResult =
+ WidgetInflater.InflationResult(
+ type = WidgetInflater.TYPE_PENDING,
+ widgetInfo = mockProviderInfo
+ )
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+
+ // When
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ verify(mockCursor).checkAndAddItem(any(), any())
+ }
+
+ @Test
+ fun `When Unrestored Pending App Widget then mark deleted`() {
+
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ mockCursor =
+ mock<LoaderCursor>().apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ id = 1
+ user = UserHandle(1)
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(appWidgetId).thenReturn(0)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ }
+ mInstallingPkgs = hashMapOf()
+ val inflationResult =
+ WidgetInflater.InflationResult(type = WidgetInflater.TYPE_PENDING, widgetInfo = null)
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ verify(mockCursor)
+ .markDeleted(
+ "processWidget: Unrestored Pending widget removed: id=1, appWidgetId=0, component=$expectedComponentName, restoreFlag:=4",
+ LauncherRestoreEventLogger.RestoreError.APP_NOT_INSTALLED
+ )
+ }
+
+ @Test
+ fun `When Pending App Widget has not started restore then update db and add item`() {
+
+ val mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(WidgetSections::class.java)
+ .startMocking()
+ try {
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ val expectedComponentName =
+ ComponentName.unflattenFromString(expectedProvider)!!.flattenToString()
+ val expectedRestoreStatus = FLAG_UI_NOT_READY or FLAG_RESTORE_STARTED
+ val expectedAppWidgetId = 0
+ mockCursor.apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ user = mUserHandle
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(appWidgetId).thenReturn(expectedAppWidgetId)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ whenever(
+ updater()
+ .put(Favorites.APPWIDGET_PROVIDER, expectedComponentName)
+ .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
+ .put(Favorites.RESTORED, expectedRestoreStatus)
+ .commit()
+ )
+ .thenReturn(1)
+ }
+ val inflationResult =
+ WidgetInflater.InflationResult(
+ type = WidgetInflater.TYPE_PENDING,
+ widgetInfo = null
+ )
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ val packageUserKey = PackageUserKey("com.google.android.testApp", mUserHandle)
+ mInstallingPkgs[packageUserKey] = PackageInstaller.SessionInfo()
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ val expectedWidgetInfo =
+ LauncherAppWidgetInfo().apply {
+ appWidgetId = expectedAppWidgetId
+ providerName = ComponentName.unflattenFromString(expectedProvider)
+ restoreStatus = expectedRestoreStatus
+ }
+ verify(
+ mockCursor
+ .updater()
+ .put(Favorites.APPWIDGET_PROVIDER, expectedProvider)
+ .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
+ .put(Favorites.RESTORED, expectedRestoreStatus)
+ )
+ .commit()
+ val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
+ verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+ val actualWidgetInfo = widgetInfoCaptor.value
+ with(actualWidgetInfo) {
+ assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
+ assertThat(restoreStatus).isEqualTo(expectedWidgetInfo.restoreStatus)
+ assertThat(targetComponent).isEqualTo(expectedWidgetInfo.targetComponent)
+ assertThat(appWidgetId).isEqualTo(expectedWidgetInfo.appWidgetId)
+ }
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
+ fun `When Archived Pending App Widget then checkAndAddItem`() {
+ val mockitoSession =
+ ExtendedMockito.mockitoSession().mockStatic(Utilities::class.java).startMocking()
+ try {
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
+ val expectedPackage = expectedComponentName!!.packageName
+ mockPmHelper =
+ mock<PackageManagerHelper>().apply {
+ whenever(isAppArchived(expectedPackage)).thenReturn(true)
+ }
+ mockCursor =
+ mock<LoaderCursor>().apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ id = 1
+ user = UserHandle(1)
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(appWidgetId).thenReturn(0)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ }
+ mInstallingPkgs = hashMapOf()
+ val inflationResult =
+ WidgetInflater.InflationResult(
+ type = WidgetInflater.TYPE_PENDING,
+ widgetInfo = null
+ )
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+
+ // When
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ verify(mockCursor).checkAndAddItem(any(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ fun `When widget inflation result is TYPE_DELETE then mark deleted`() {
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
+ val expectedPackage = expectedComponentName!!.packageName
+ mockPmHelper =
+ mock<PackageManagerHelper>().apply {
+ whenever(isAppArchived(expectedPackage)).thenReturn(true)
+ }
+ mockCursor =
+ mock<LoaderCursor>().apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ id = 1
+ user = UserHandle(1)
+ container = CONTAINER_DESKTOP
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ }
+ mInstallingPkgs = hashMapOf()
+ val inflationResult =
+ WidgetInflater.InflationResult(
+ type = WidgetInflater.TYPE_DELETE,
+ widgetInfo = null,
+ reason = "test_delete_reason",
+ restoreErrorType = MISSING_WIDGET_PROVIDER
+ )
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+
+ // When
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ verify(mockCursor).markDeleted(inflationResult.reason, inflationResult.restoreErrorType)
+ }
}
diff --git a/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt b/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
new file mode 100644
index 0000000..cc8e61d
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model.gridmigration
+
+import android.content.ContentValues
+import android.database.sqlite.SQLiteDatabase
+import android.graphics.Point
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.celllayout.board.CellLayoutBoard
+
+class MockSet(override val size: Int) : Set<String> {
+ override fun contains(element: String): Boolean = true
+ override fun containsAll(elements: Collection<String>): Boolean = true
+ override fun isEmpty(): Boolean = false
+ override fun iterator(): Iterator<String> = listOf<String>().iterator()
+}
+
+fun itemListToBoard(itemsArg: List<WorkspaceItem>, boardSize: Point): List<CellLayoutBoard> {
+ val items = itemsArg.filter { it.container != Favorites.CONTAINER_HOTSEAT }
+ val boardList =
+ List(items.maxOf { it.screenId + 1 }) { CellLayoutBoard(boardSize.x, boardSize.y) }
+ items.forEach {
+ when (it.type) {
+ Favorites.ITEM_TYPE_FOLDER,
+ Favorites.ITEM_TYPE_APP_PAIR -> throw Exception("Not implemented")
+ Favorites.ITEM_TYPE_APPWIDGET ->
+ boardList[it.screenId].addWidget(it.x, it.y, it.spanX, it.spanY)
+ Favorites.ITEM_TYPE_APPLICATION -> boardList[it.screenId].addIcon(it.x, it.y)
+ }
+ }
+ return boardList
+}
+
+fun insertIntoDb(tableName: String, entry: WorkspaceItem, db: SQLiteDatabase) {
+ val values = ContentValues()
+ values.put(Favorites.SCREEN, entry.screenId)
+ values.put(Favorites.CELLX, entry.x)
+ values.put(Favorites.CELLY, entry.y)
+ values.put(Favorites.SPANX, entry.spanX)
+ values.put(Favorites.SPANY, entry.spanY)
+ values.put(Favorites.TITLE, entry.title)
+ values.put(Favorites.INTENT, entry.intent)
+ values.put(Favorites.APPWIDGET_PROVIDER, entry.appWidgetProvider)
+ values.put(Favorites.APPWIDGET_ID, entry.appWidgetId)
+ values.put(Favorites.CONTAINER, entry.container)
+ values.put(Favorites.ITEM_TYPE, entry.type)
+ values.put(Favorites._ID, entry.id)
+ db.insert(tableName, null, values)
+}
+
+fun readDb(tableName: String, db: SQLiteDatabase): List<WorkspaceItem> {
+ val result = mutableListOf<WorkspaceItem>()
+ val cursor = db.query(tableName, null, null, null, null, null, null)
+ val indexCellX: Int = cursor.getColumnIndexOrThrow(Favorites.CELLX)
+ val indexCellY: Int = cursor.getColumnIndexOrThrow(Favorites.CELLY)
+ val indexSpanX: Int = cursor.getColumnIndexOrThrow(Favorites.SPANX)
+ val indexSpanY: Int = cursor.getColumnIndexOrThrow(Favorites.SPANY)
+ val indexId: Int = cursor.getColumnIndexOrThrow(Favorites._ID)
+ val indexScreen: Int = cursor.getColumnIndexOrThrow(Favorites.SCREEN)
+ val indexTitle: Int = cursor.getColumnIndexOrThrow(Favorites.TITLE)
+ val indexAppWidgetId: Int = cursor.getColumnIndexOrThrow(Favorites.APPWIDGET_ID)
+ val indexWidgetProvider: Int = cursor.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER)
+ val indexIntent: Int = cursor.getColumnIndexOrThrow(Favorites.INTENT)
+ val indexItemType: Int = cursor.getColumnIndexOrThrow(Favorites.ITEM_TYPE)
+ val container: Int = cursor.getColumnIndexOrThrow(Favorites.CONTAINER)
+ while (cursor.moveToNext()) {
+ result.add(
+ WorkspaceItem(
+ x = cursor.getInt(indexCellX),
+ y = cursor.getInt(indexCellY),
+ spanX = cursor.getInt(indexSpanX),
+ spanY = cursor.getInt(indexSpanY),
+ id = cursor.getInt(indexId),
+ screenId = cursor.getInt(indexScreen),
+ title = cursor.getString(indexTitle),
+ appWidgetId = cursor.getInt(indexAppWidgetId),
+ appWidgetProvider = cursor.getString(indexWidgetProvider),
+ intent = cursor.getString(indexIntent),
+ type = cursor.getInt(indexItemType),
+ container = cursor.getInt(container)
+ )
+ )
+ }
+ return result
+}
+
+data class WorkspaceItem(
+ val x: Int,
+ val y: Int,
+ val spanX: Int,
+ val spanY: Int,
+ val id: Int,
+ val screenId: Int,
+ val title: String,
+ val appWidgetId: Int,
+ val appWidgetProvider: String,
+ val intent: String,
+ val type: Int,
+ val container: Int,
+)
diff --git a/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt b/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
new file mode 100644
index 0000000..58b915f
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model.gridmigration
+
+import android.content.Context
+import android.database.sqlite.SQLiteDatabase
+import android.graphics.Point
+import android.os.Process
+import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.celllayout.testgenerator.ValidGridMigrationTestCaseGenerator
+import com.android.launcher3.celllayout.testgenerator.generateItemsForTest
+import com.android.launcher3.model.DatabaseHelper
+import com.android.launcher3.model.DeviceGridState
+import com.android.launcher3.model.GridSizeMigrationUtil
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.provider.LauncherDbUtils
+import com.android.launcher3.util.rule.TestStabilityRule
+import com.android.launcher3.util.rule.TestStabilityRule.Stability
+import java.util.Random
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private data class Grid(val tableName: String, val size: Point, val items: List<WorkspaceItem>) {
+ fun toGridState(): DeviceGridState =
+ DeviceGridState(size.x, size.y, size.x, InvariantDeviceProfile.TYPE_PHONE, tableName)
+}
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ValidGridMigrationUnitTest {
+
+ companion object {
+ const val SEED = 1044542
+ val REPEAT_AFTER = Point(0, 10)
+ val REPEAT_AFTER_DST = Point(6, 15)
+ const val TAG = "ValidGridMigrationUnitTest"
+ const val SMALL_TEST_SIZE = 60
+ const val LARGE_TEST_SIZE = 1000
+ }
+
+ private lateinit var context: Context
+
+ @Before
+ fun setUp() {
+ context = InstrumentationRegistry.getInstrumentation().targetContext
+ }
+
+ private fun validate(srcGrid: Grid, dstGrid: Grid, resultItems: List<WorkspaceItem>) {
+ // This returns a map with the number of repeated elements
+ // ex { calculatorIcon : 6, weatherWidget : 2 }
+ val itemsToMap = { it: List<WorkspaceItem> ->
+ it.filter { it.container != Favorites.CONTAINER_HOTSEAT }
+ .groupingBy {
+ when (it.type) {
+ Favorites.ITEM_TYPE_FOLDER,
+ Favorites.ITEM_TYPE_APP_PAIR -> throw Exception("Not implemented")
+ Favorites.ITEM_TYPE_APPWIDGET -> it.appWidgetProvider
+ Favorites.ITEM_TYPE_APPLICATION -> it.intent
+ else -> it.title
+ }
+ }
+ .eachCount()
+ }
+ resultItems.forEach {
+ assert((it.x in 0..dstGrid.size.x) && (it.y in 0..dstGrid.size.y)) {
+ "Item outside of the board size. Size = ${dstGrid.size} Item = $it"
+ }
+ assert(
+ (it.x + it.spanX in 0..dstGrid.size.x) && (it.y + it.spanY in 0..dstGrid.size.y)
+ ) {
+ "Item doesn't fit in the grid. Size = ${dstGrid.size} Item = $it"
+ }
+ }
+
+ val srcCountMap = itemsToMap(srcGrid.items)
+ val resultCountMap = itemsToMap(resultItems)
+ val diff = resultCountMap - srcCountMap
+
+ diff.forEach { (k, count) ->
+ assert(count >= 0) { "Source item $k not present on the result" }
+ }
+ }
+
+ private fun addItemsToDb(db: SQLiteDatabase, grid: Grid) {
+ LauncherDbUtils.SQLiteTransaction(db).use { transaction ->
+ grid.items.forEach { insertIntoDb(grid.tableName, it, transaction.db) }
+ transaction.commit()
+ }
+ }
+
+ private fun migrate(
+ srcGrid: Grid,
+ dstGrid: Grid,
+ ): List<WorkspaceItem> {
+ val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle())
+ val dbHelper =
+ DatabaseHelper(
+ context,
+ null,
+ { UserCache.INSTANCE.get(context).getSerialNumberForUser(it) },
+ {}
+ )
+
+ Favorites.addTableToDb(dbHelper.writableDatabase, userSerial, false, srcGrid.tableName)
+
+ addItemsToDb(dbHelper.writableDatabase, srcGrid)
+ addItemsToDb(dbHelper.writableDatabase, dstGrid)
+
+ LauncherDbUtils.SQLiteTransaction(dbHelper.writableDatabase).use {
+ GridSizeMigrationUtil.migrate(
+ dbHelper,
+ GridSizeMigrationUtil.DbReader(it.db, srcGrid.tableName, context, MockSet(1)),
+ GridSizeMigrationUtil.DbReader(it.db, dstGrid.tableName, context, MockSet(1)),
+ dstGrid.size.x,
+ dstGrid.size,
+ srcGrid.toGridState(),
+ dstGrid.toGridState()
+ )
+ it.commit()
+ }
+ return readDb(dstGrid.tableName, dbHelper.readableDatabase)
+ }
+
+ @Test
+ fun runTestCase() {
+ val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong()))
+ for (i in 0..SMALL_TEST_SIZE) {
+ val testCase = caseGenerator.generateTestCase(isDestEmpty = true)
+ Log.d(TAG, "Test case = $testCase")
+ val srcGrid =
+ Grid(
+ tableName = Favorites.TMP_TABLE,
+ size = testCase.srcSize,
+ items = generateItemsForTest(testCase.boards, REPEAT_AFTER)
+ )
+ val dstGrid =
+ Grid(tableName = Favorites.TABLE_NAME, size = testCase.targetSize, items = listOf())
+ validate(srcGrid, dstGrid, migrate(srcGrid, dstGrid))
+ }
+ }
+
+ @Test
+ fun mergeBoards() {
+ val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong()))
+ for (i in 0..SMALL_TEST_SIZE) {
+ val testCase = caseGenerator.generateTestCase(isDestEmpty = false)
+ Log.d(TAG, "Test case = $testCase")
+ val srcGrid =
+ Grid(
+ tableName = Favorites.TMP_TABLE,
+ size = testCase.srcSize,
+ items = generateItemsForTest(testCase.boards, REPEAT_AFTER)
+ )
+ val dstGrid =
+ Grid(
+ tableName = Favorites.TABLE_NAME,
+ size = testCase.targetSize,
+ items = generateItemsForTest(testCase.destBoards, REPEAT_AFTER_DST)
+ )
+ validate(srcGrid, dstGrid, migrate(srcGrid, dstGrid))
+ }
+ }
+
+ // This test takes about 4 minutes, there is no need to run it in presubmit.
+ @Stability(flavors = TestStabilityRule.LOCAL or TestStabilityRule.PLATFORM_POSTSUBMIT)
+ @Test
+ fun runExtensiveTestCases() {
+ val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong()))
+ for (i in 0..LARGE_TEST_SIZE) {
+ val testCase = caseGenerator.generateTestCase(isDestEmpty = true)
+ Log.d(TAG, "Test case = $testCase")
+ val srcGrid =
+ Grid(
+ tableName = Favorites.TMP_TABLE,
+ size = testCase.srcSize,
+ items = generateItemsForTest(testCase.boards, REPEAT_AFTER)
+ )
+ val dstGrid =
+ Grid(tableName = Favorites.TABLE_NAME, size = testCase.targetSize, items = listOf())
+ validate(srcGrid, dstGrid, migrate(srcGrid, dstGrid))
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index 9409ac1..60385a7 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -15,137 +15,105 @@
*/
package com.android.launcher3.nonquickstep
-import android.content.Context
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags
import com.android.launcher3.InvariantDeviceProfile
-import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
/** Tests for DeviceProfile. */
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
class DeviceProfileDumpTest : AbstractDeviceProfileTest() {
private val folderName: String = "DeviceProfileDumpTest"
- @Test
- fun phonePortrait3Button() {
- initializeVarsForPhone(deviceSpecs["phone"]!!, isGestureMode = false)
- val dp = getDeviceProfileForGrid("5_by_5")
- assertDump(dp, "phonePortrait3Button")
+ @Parameterized.Parameter lateinit var instance: TestCase
+
+ @Before
+ fun setUp() {
+ if (instance.decoupleDepth) {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION)
+ } else {
+ setFlagsRule.disableFlags(Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION)
+ }
}
@Test
- fun phonePortrait() {
- initializeVarsForPhone(deviceSpecs["phone"]!!)
- val dp = getDeviceProfileForGrid("5_by_5")
+ fun dumpPortraitGesture() {
+ initializeDevice(instance.deviceName, isGestureMode = true, isLandscape = false)
+ val dp = getDeviceProfileForGrid(instance.gridName)
+ dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
- assertDump(dp, "phonePortrait")
+ assertDump(dp, instance.filename("Portrait"))
}
@Test
- fun phoneVerticalBar3Button() {
- initializeVarsForPhone(deviceSpecs["phone"]!!, isVerticalBar = true, isGestureMode = false)
- val dp = getDeviceProfileForGrid("5_by_5")
+ fun dumpPortrait3Button() {
+ initializeDevice(instance.deviceName, isGestureMode = false, isLandscape = false)
+ val dp = getDeviceProfileForGrid(instance.gridName)
+ dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
- assertDump(dp, "phoneVerticalBar3Button")
+ assertDump(dp, instance.filename("Portrait3Button"))
}
@Test
- fun phoneVerticalBar() {
- initializeVarsForPhone(deviceSpecs["phone"]!!, isVerticalBar = true)
- val dp = getDeviceProfileForGrid("5_by_5")
+ fun dumpLandscapeGesture() {
+ initializeDevice(instance.deviceName, isGestureMode = true, isLandscape = true)
+ val dp = getDeviceProfileForGrid(instance.gridName)
+ dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
- assertDump(dp, "phoneVerticalBar")
+ val testName =
+ if (instance.deviceName == "phone") {
+ "VerticalBar"
+ } else {
+ "Landscape"
+ }
+ assertDump(dp, instance.filename(testName))
}
@Test
- fun tabletLandscape3Button() {
- initializeVarsForTablet(deviceSpecs["tablet"]!!, isLandscape = true, isGestureMode = false)
- val dp = getDeviceProfileForGrid("6_by_5")
- dp.isTaskbarPresentInApps = true
+ fun dumpLandscape3Button() {
+ initializeDevice(instance.deviceName, isGestureMode = false, isLandscape = true)
+ val dp = getDeviceProfileForGrid(instance.gridName)
+ dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
- assertDump(dp, "tabletLandscape3Button")
+ val testName =
+ if (instance.deviceName == "phone") {
+ "VerticalBar3Button"
+ } else {
+ "Landscape3Button"
+ }
+ assertDump(dp, instance.filename(testName))
}
- @Test
- fun tabletLandscape() {
- initializeVarsForTablet(deviceSpecs["tablet"]!!, isLandscape = true)
- val dp = getDeviceProfileForGrid("6_by_5")
- dp.isTaskbarPresentInApps = true
-
- assertDump(dp, "tabletLandscape")
- }
-
- @Test
- fun tabletPortrait3Button() {
- initializeVarsForTablet(deviceSpecs["tablet"]!!, isGestureMode = false)
- val dp = getDeviceProfileForGrid("6_by_5")
- dp.isTaskbarPresentInApps = true
-
- assertDump(dp, "tabletPortrait3Button")
- }
-
- @Test
- fun tabletPortrait() {
- initializeVarsForTablet(deviceSpecs["tablet"]!!)
- val dp = getDeviceProfileForGrid("6_by_5")
- dp.isTaskbarPresentInApps = true
-
- assertDump(dp, "tabletPortrait")
- }
-
- @Test
- fun twoPanelLandscape3Button() {
- initializeVarsForTwoPanel(
- deviceSpecs["twopanel-tablet"]!!,
- deviceSpecs["twopanel-phone"]!!,
- isLandscape = true,
- isGestureMode = false
- )
- val dp = getDeviceProfileForGrid("4_by_4")
- dp.isTaskbarPresentInApps = true
-
- assertDump(dp, "twoPanelLandscape3Button")
- }
-
- @Test
- fun twoPanelLandscape() {
- initializeVarsForTwoPanel(
- deviceSpecs["twopanel-tablet"]!!,
- deviceSpecs["twopanel-phone"]!!,
- isLandscape = true
- )
- val dp = getDeviceProfileForGrid("4_by_4")
- dp.isTaskbarPresentInApps = true
-
- assertDump(dp, "twoPanelLandscape")
- }
-
- @Test
- fun twoPanelPortrait3Button() {
- initializeVarsForTwoPanel(
- deviceSpecs["twopanel-tablet"]!!,
- deviceSpecs["twopanel-phone"]!!,
- isGestureMode = false
- )
- val dp = getDeviceProfileForGrid("4_by_4")
- dp.isTaskbarPresentInApps = true
-
- assertDump(dp, "twoPanelPortrait3Button")
- }
-
- @Test
- fun twoPanelPortrait() {
- initializeVarsForTwoPanel(deviceSpecs["twopanel-tablet"]!!, deviceSpecs["twopanel-phone"]!!)
- val dp = getDeviceProfileForGrid("4_by_4")
- dp.isTaskbarPresentInApps = true
-
- assertDump(dp, "twoPanelPortrait")
+ private fun initializeDevice(deviceName: String, isGestureMode: Boolean, isLandscape: Boolean) {
+ val deviceSpec = deviceSpecs[instance.deviceName]!!
+ when (deviceName) {
+ "twopanel-phone",
+ "twopanel-tablet" ->
+ initializeVarsForTwoPanel(
+ deviceSpecUnfolded = deviceSpecs["twopanel-tablet"]!!,
+ deviceSpecFolded = deviceSpecs["twopanel-phone"]!!,
+ isLandscape = isLandscape,
+ isGestureMode = isGestureMode,
+ )
+ "tablet" ->
+ initializeVarsForTablet(
+ deviceSpec = deviceSpec,
+ isLandscape = isLandscape,
+ isGestureMode = isGestureMode
+ )
+ else ->
+ initializeVarsForPhone(
+ deviceSpec = deviceSpec,
+ isVerticalBar = isLandscape,
+ isGestureMode = isGestureMode
+ )
+ }
}
private fun getDeviceProfileForGrid(gridName: String): DeviceProfile {
@@ -153,6 +121,48 @@
}
private fun assertDump(dp: DeviceProfile, filename: String) {
- assertDump(dp, folderName, filename);
+ assertDump(dp, folderName, filename)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getInstances(): List<TestCase> {
+ return listOf(
+ TestCase("phone", gridName = "5_by_5"),
+ TestCase("tablet", gridName = "6_by_5", isTaskbarPresentInApps = true),
+ TestCase("twopanel-tablet", gridName = "4_by_4", isTaskbarPresentInApps = true),
+ TestCase(
+ "twopanel-tablet",
+ gridName = "4_by_4",
+ isTaskbarPresentInApps = true,
+ decoupleDepth = true
+ ),
+ )
+ }
+
+ data class TestCase(
+ val deviceName: String,
+ val gridName: String,
+ val isTaskbarPresentInApps: Boolean = false,
+ val decoupleDepth: Boolean = false
+ ) {
+ fun filename(testName: String = ""): String {
+ val device =
+ when (deviceName) {
+ "tablet" -> "tablet"
+ "twopanel-tablet" -> "twoPanel"
+ "twopanel-phone" -> "twoPanelFolded"
+ else -> "phone"
+ }
+ val depth =
+ if (decoupleDepth) {
+ "_decoupleDepth"
+ } else {
+ ""
+ }
+ return "$device$testName$depth"
+ }
+ }
}
}
diff --git a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
index 9912a34..408691c 100644
--- a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
@@ -42,6 +42,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(145)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1445)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(177)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(177)
@@ -64,6 +66,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(72)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1080)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(110)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(110)
@@ -85,6 +89,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(104)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1468)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(370)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(370)
@@ -100,22 +106,21 @@
@Test
fun nav_buttons_dont_interfere_with_required_hotseat_width() {
initializeVarsForTablet(isGestureMode = false, isLandscape = true)
- inv?.apply {
- hotseatColumnSpan = IntArray(4) { 4 }
- inlineQsb = BooleanArray(4) { false }
- }
+ inv?.apply { inlineQsb = BooleanArray(4) { false } }
val dp = newDP()
dp.isTaskbarPresentInApps = true
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
- assertThat(dp.hotseatBorderSpace).isEqualTo(100)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(248)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1960)
- assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(668)
- assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(668)
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(300)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(300)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1214)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1950)
}
/** This is a case when after setting the hotseat, the QSB width needs to be changed to fit */
@@ -128,13 +133,15 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
- assertThat(dp.hotseatBorderSpace).isEqualTo(91)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(233)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1885)
- assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(640)
- assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(640)
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(287)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(287)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1169)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1875)
}
/**
@@ -150,13 +157,15 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
- assertThat(dp.hotseatBorderSpace).isEqualTo(75)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(205)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1745)
- assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(582)
- assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(582)
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(257)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(257)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1085)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1735)
}
@Test
@@ -179,4 +188,23 @@
assertThat(dp.hotseatQsbWidth).isEqualTo(1435)
}
}
+
+ @Test
+ fun increase_span_when_space_between_icons_is_less_than_minimum() {
+ initializeVarsForTwoPanel(isGestureMode = false, isLandscape = false, rows = 5, cols = 5)
+ val dp = newDP()
+ dp.isTaskbarPresentInApps = true
+
+ assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+ assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(112)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(8)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1383)
+
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(228)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(228)
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1372)
+ }
}
diff --git a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
index e459956..98b6b4b 100644
--- a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
+++ b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
@@ -30,6 +30,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -48,7 +49,6 @@
import android.view.View;
import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.allapps.PrivateProfileManager;
@@ -56,8 +56,10 @@
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.ApiWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
+import com.android.launcher3.util.LauncherMultivalentJUnit;
import com.android.launcher3.util.TestSandboxModelContextWrapper;
import com.android.launcher3.util.UserIconInfo;
import com.android.launcher3.views.BaseDragLayer;
@@ -74,7 +76,7 @@
import java.util.ArrayList;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(LauncherMultivalentJUnit.class)
public class SystemShortcutTest {
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
private static final UserHandle PRIVATE_HANDLE = new UserHandle(11);
@@ -87,6 +89,7 @@
private PopupDataProvider mPopupDataProvider;
private AppInfo mAppInfo;
@Mock UserCache mUserCache;
+ @Mock ApiWrapper mApiWrapper;
@Mock BaseDragLayer mBaseDragLayer;
@Mock UserIconInfo mUserIconInfo;
@Mock LauncherActivityInfo mLauncherActivityInfo;
@@ -97,6 +100,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mSandboxContext.putObject(UserCache.INSTANCE, mUserCache);
+ mSandboxContext.putObject(ApiWrapper.INSTANCE, mApiWrapper);
mTestContext = new TestSandboxModelContextWrapper(mSandboxContext);
mView = new View(mSandboxContext);
spyOn(mTestContext);
@@ -244,7 +248,6 @@
SystemShortcut systemShortcut = SystemShortcut.PRIVATE_PROFILE_INSTALL
.getShortcut(mTestContext, mAppInfo, mView);
- verify(mPrivateProfileManager, times(2)).getProfileUser();
assertNull(systemShortcut);
}
@@ -266,8 +269,7 @@
SystemShortcut systemShortcut = SystemShortcut.PRIVATE_PROFILE_INSTALL
.getShortcut(mTestContext, mAppInfo, mView);
- verify(mPrivateProfileManager, times(3)).getProfileUser();
- verify(mPrivateProfileManager).isEnabled();
+ verify(mPrivateProfileManager, atLeast(1)).isEnabled();
assertNotNull(systemShortcut);
}
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 733f1e9..b3675a6 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -38,7 +38,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.backup.BackupManager;
@@ -243,7 +243,8 @@
// Then
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
- verifyZeroInteractions(mMockController);
+ // b/343530737
+ verifyNoMoreInteractions(mMockController);
}
@Test
diff --git a/tests/src/com/android/launcher3/secondarydisplay/TaplSecondaryDisplayLauncherTest.java b/tests/src/com/android/launcher3/secondarydisplay/TaplSecondaryDisplayLauncherTest.java
deleted file mode 100644
index d7b9638..0000000
--- a/tests/src/com/android/launcher3/secondarydisplay/TaplSecondaryDisplayLauncherTest.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2021 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.secondarydisplay;
-
-import static android.content.Context.MODE_PRIVATE;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.view.MotionEvent.ACTION_DOWN;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Intent;
-import android.graphics.Point;
-import android.os.SystemClock;
-import android.view.MotionEvent;
-import android.widget.TextView;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
-import com.android.launcher3.tapl.LauncherInstrumentation;
-import com.android.launcher3.ui.AbstractLauncherUiTest;
-
-import org.junit.After;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link SecondaryDisplayLauncher}.
- * TODO (b/242776943): Remove anti-patterns & migrate prediction row tests to Quickstep directory
- */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public final class TaplSecondaryDisplayLauncherTest extends AbstractLauncherUiTest {
- private static final int WAIT_TIME_MS = 5000;
- private static final int LONG_PRESS_DURATION_MS = 1000;
- private static final int DRAG_TIME_MS = 160;
-
- private static final String PINNED_APPS_KEY = "pinned_apps";
-
- // Variables required to coordinate drag steps.
- private Point mStartPoint;
- private Point mEndPoint;
- private long mDownTime;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- setDragNDropFlag(true);
- }
-
- @After
- public void tearDown() {
- mTargetContext.getSharedPreferences(PINNED_APPS_KEY, MODE_PRIVATE)
- .edit().clear().commit();
- }
-
- @Test
- @Ignore
- public void initializeSecondaryDisplayLauncher_allAppsButtonVisible() {
- assertThat(findObjectByResourceName("all_apps_button")).isNotNull();
- }
-
- @Test
- @Ignore
- public void allAppsButtonTap_opensAppDrawer() {
- openAppDrawer();
- assertThat(findObjectByResourceName("search_container_all_apps")).isNotNull();
- }
-
- @Test
- @Ignore("Launcher3 without quickstep doesn't have a predictions row.")
- public void appDrawerOpened_predictionRowAppDividerVisible() {
- openAppDrawer();
- assertThat(findObjectByResourceName("apps_divider_view")).isNotNull();
- }
-
- @Test
- @Ignore
- public void dragNDropDisabled_pinIconAddsToWorkspace() {
- setDragNDropFlag(false);
- openAppDrawer();
- UiObject2 app = findDescendantByResourceName(
- findObjectByResourceName("apps_list_view"), "icon");
- app.click(LONG_PRESS_DURATION_MS);
- UiObject2 popupContainer = findObjectByResourceName("popup_container");
- assertThat(popupContainer).isNotNull();
- UiObject2 pinIcon = findDescendantByTextOrDesc(popupContainer, "Add to home screen");
- assertThat(pinIcon).isNotNull();
- pinIcon.click();
- String appName = app.getContentDescription();
- assertThat(findAppInWorkspace(appName)).isNotNull();
- }
-
- @Test
- @Ignore
- public void pressBackFromAllApps_popupMenuOpen_returnsToWorkspace() {
- openAppDrawer();
- assertThat(findObjectByResourceName("search_container_all_apps")).isNotNull();
-
- findDescendantByResourceName(findObjectByResourceName("apps_list_view"), "icon")
- .click(LONG_PRESS_DURATION_MS);
- assertThat(findObjectByResourceName("popup_container")).isNotNull();
-
- // First back press should close only popup menu.
- mDevice.pressBack();
- assertThat(findObjectByResourceName("search_container_all_apps")).isNotNull();
- assertThat(findObjectByResourceName("popup_container")).isNull();
-
- // Second back press should close app drawer.
- mDevice.pressBack();
- assertThat(findObjectByResourceName("popup_container")).isNull();
- assertThat(findObjectByResourceName("search_container_all_apps")).isNull();
- }
-
- @Test
- @Ignore("Launcher3 without quickstep doesn't have a predictions row.")
- public void dragNDropFromPredictionsRow_pinToGrid() {
- openAppDrawer();
- assertThat(findObjectByResourceName("prediction_row")).isNotNull();
- String appName = startDragFromPredictionRow();
- moveAppToCenterOfScreen();
- dropApp();
-
- // Ensure app was added.
- assertThat(findAppInWorkspace(appName)).isNotNull();
- }
-
- @Test
- @Ignore
- public void dragNDropFromAppDrawer_pinToGrid() {
- openAppDrawer();
- String draggedAppName = startDragFromAllApps();
- moveAppToCenterOfScreen();
- dropApp();
-
- // Ensure app was added.
- assertThat(findAppInWorkspace(draggedAppName)).isNotNull();
- }
-
- @Test
- @Ignore
- public void tapRemoveButton_unpinApp() {
- openAppDrawer();
- String draggedAppName = startDragFromAllApps();
- moveAppToCenterOfScreen();
- dropApp();
- removeAppByName(draggedAppName);
- assertThat(findAppInWorkspace(draggedAppName)).isNull();
- }
-
- private void openAppDrawer() {
- UiObject2 allAppsButton = findObjectByResourceName("all_apps_button");
- assertThat(allAppsButton).isNotNull();
- allAppsButton.click();
- }
-
- private String startDragFromAllApps() {
- // Find app from app drawer.
- UiObject2 allApps = findObjectByResourceName("apps_list_view");
- assertThat(allApps).isNotNull();
- UiObject2 icon = findDescendantByResourceName(allApps, "icon");
- assertThat(icon).isNotNull();
- String appName = icon.getContentDescription();
-
- // Start drag action.
- mDownTime = SystemClock.uptimeMillis();
- mStartPoint = icon.getVisibleCenter();
- mEndPoint = new Point(mStartPoint.x, mStartPoint.y);
- mLauncher.sendPointer(mDownTime, mDownTime, ACTION_DOWN, mStartPoint,
- LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
- assertThat(findObjectByResourceName("popup_container")).isNotNull();
- return appName;
- }
-
- private String startDragFromPredictionRow() {
- // Find app from predictions.
- UiObject2 predictionRow = findObjectByResourceName("prediction_row");
- assertThat(predictionRow).isNotNull();
-
- UiObject2 icon = findDescendantByResourceName(predictionRow, "icon");
- assertThat(icon).isNotNull();
-
- String appName = icon.getContentDescription();
- UiObject2 app = findDescendantByAppName(predictionRow, appName);
- assertThat(app).isNotNull();
-
- // Start drag action.
- mDownTime = SystemClock.uptimeMillis();
- mStartPoint = icon.getVisibleCenter();
- mEndPoint = new Point(mStartPoint.x, mStartPoint.y);
- mLauncher.sendPointer(mDownTime, mDownTime, ACTION_DOWN, mStartPoint,
- LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
- assertThat(findObjectByResourceName("popup_container")).isNotNull();
- return appName;
- }
-
- private void moveAppToCenterOfScreen() {
- mEndPoint.set(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() / 2);
- mLauncher.movePointer(mDownTime, SystemClock.uptimeMillis(), DRAG_TIME_MS, true,
- mStartPoint, mEndPoint, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
- }
-
- private void dropApp() {
- mLauncher.sendPointer(mDownTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP,
- mEndPoint, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
- }
-
- private void removeAppByName(String appName) {
- // Find app within home screen.
- UiObject2 app = findDescendantByAppName(findObjectByResourceName("workspace_grid"),
- appName);
- if (app == null) return;
-
- // Open app's popup container.
- app.click(LONG_PRESS_DURATION_MS);
- UiObject2 popupContainer = findObjectByResourceName("popup_container");
- assertThat(popupContainer).isNotNull();
-
- // Grab & click remove button.
- UiObject2 removeButton = findDescendantByTextOrDesc(popupContainer, "Remove");
- assertThat(removeButton).isNotNull();
- removeButton.click();
- }
-
- private UiObject2 findAppInWorkspace(String appName) {
- UiObject2 workspace = findObjectByResourceName("workspace_grid");
- return findDescendantByAppName(workspace, appName);
- }
-
- private UiObject2 findObjectByResourceName(String resourceName) {
- return mDevice.wait(Until.findObject(By.res(mTargetPackage, resourceName)), WAIT_TIME_MS);
- }
-
- private UiObject2 findDescendantByResourceName(UiObject2 outerObject,
- String resourceName) {
- assertThat(outerObject).isNotNull();
- return outerObject.findObject(By.res(mTargetPackage, resourceName));
- }
-
- private UiObject2 findDescendantByAppName(UiObject2 outerObject, String appName) {
- assertThat(outerObject).isNotNull();
- return outerObject.findObject(By.clazz(TextView.class).text(appName)
- .pkg(mDevice.getLauncherPackageName()));
- }
-
- private UiObject2 findDescendantByTextOrDesc(UiObject2 outerObject, String content) {
- assertThat(outerObject).isNotNull();
- UiObject2 innerObject = outerObject.findObject(By.desc(content));
- if (innerObject == null) innerObject = outerObject.findObject(By.text(content));
- return innerObject;
- }
-
- private void startSecondaryDisplayActivity() {
- mTargetContext.startActivity((
- new Intent(mTargetContext, SecondaryDisplayLauncher.class).addFlags(
- FLAG_ACTIVITY_NEW_TASK)));
- }
-
- private void setDragNDropFlag(Boolean status) {
- startSecondaryDisplayActivity();
- }
-}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 1f824b8..a6f4441 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -20,6 +20,7 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
+import static com.android.launcher3.testing.shared.TestProtocol.WIDGET_CONFIG_NULL_EXTRA_INTENT;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static org.junit.Assert.assertEquals;
@@ -55,6 +56,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
import com.android.launcher3.tapl.HomeAllApps;
import com.android.launcher3.tapl.HomeAppIcon;
import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -64,6 +66,7 @@
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.ExtendedLongPressTimeoutRule;
import com.android.launcher3.util.rule.FailureWatcher;
import com.android.launcher3.util.rule.SamplerRule;
import com.android.launcher3.util.rule.ScreenRecordRule;
@@ -92,10 +95,10 @@
/**
* Base class for all instrumentation tests providing various utility methods.
*/
-public abstract class AbstractLauncherUiTest {
+public abstract class AbstractLauncherUiTest<LAUNCHER_TYPE extends Launcher> {
public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
- public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
+ public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 10;
public static final long DEFAULT_UI_TIMEOUT = TestUtil.DEFAULT_UI_TIMEOUT;
private static final String TAG = "AbstractLauncherUiTest";
@@ -217,6 +220,9 @@
@Rule
public SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+ @Rule
+ public ExtendedLongPressTimeoutRule mLongPressTimeoutRule = new ExtendedLongPressTimeoutRule();
+
public static void initialize(AbstractLauncherUiTest test) throws Exception {
test.reinitializeLauncherData();
test.mDevice.pressHome();
@@ -245,7 +251,7 @@
final ViewCaptureRule viewCaptureRule = new ViewCaptureRule(
Launcher.ACTIVITY_TRACKER::getCreatedActivity);
final RuleChain inner = RuleChain
- .outerRule(new PortraitLandscapeRunner(this))
+ .outerRule(new PortraitLandscapeRunner<LAUNCHER_TYPE>(this))
.around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
// .around(viewCaptureRule) // b/315482167
.around(new TestIsolationRule(mLauncher, true));
@@ -294,7 +300,8 @@
if (userManager != null) {
for (UserHandle userHandle : userManager.getUserProfiles()) {
if (!userHandle.isSystem()) {
- mDevice.executeShellCommand("pm remove-user " + userHandle.getIdentifier());
+ mDevice.executeShellCommand(
+ "pm remove-user --wait " + userHandle.getIdentifier());
}
}
}
@@ -420,12 +427,12 @@
}
}
- protected <T> T getFromLauncher(Function<Launcher, T> f) {
+ protected <T> T getFromLauncher(Function<LAUNCHER_TYPE, T> f) {
if (!TestHelpers.isInLauncherProcess()) return null;
return getOnUiThread(() -> f.apply(Launcher.ACTIVITY_TRACKER.getCreatedActivity()));
}
- protected void executeOnLauncher(Consumer<Launcher> f) {
+ protected void executeOnLauncher(Consumer<LAUNCHER_TYPE> f) {
getFromLauncher(launcher -> {
f.accept(launcher);
return null;
@@ -435,7 +442,7 @@
// Execute an action on Launcher, but forgive it when launcher is null.
// Launcher can be null if teardown is happening after a failed setup step where launcher
// activity failed to be created.
- protected void executeOnLauncherInTearDown(Consumer<Launcher> f) {
+ protected void executeOnLauncherInTearDown(Consumer<LAUNCHER_TYPE> f) {
executeOnLauncher(launcher -> {
if (launcher != null) f.accept(launcher);
});
@@ -463,20 +470,20 @@
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
protected void waitForLauncherCondition(String
- message, Function<Launcher, Boolean> condition) {
+ message, Function<LAUNCHER_TYPE, Boolean> condition) {
waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT);
}
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
- protected <T> T getOnceNotNull(String message, Function<Launcher, T> f) {
+ protected <O> O getOnceNotNull(String message, Function<LAUNCHER_TYPE, O> f) {
return getOnceNotNull(message, f, DEFAULT_ACTIVITY_TIMEOUT);
}
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
protected void waitForLauncherCondition(
- String message, Function<Launcher, Boolean> condition, long timeout) {
+ String message, Function<LAUNCHER_TYPE, Boolean> condition, long timeout) {
verifyKeyguardInvisible();
if (!TestHelpers.isInLauncherProcess()) return;
Wait.atMost(message, () -> getFromLauncher(condition), timeout, mLauncher);
@@ -484,7 +491,7 @@
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
- protected <T> T getOnceNotNull(String message, Function<Launcher, T> f, long timeout) {
+ protected <T> T getOnceNotNull(String message, Function<LAUNCHER_TYPE, T> f, long timeout) {
if (!TestHelpers.isInLauncherProcess()) return null;
final Object[] output = new Object[1];
@@ -500,7 +507,7 @@
// flakiness.
protected void waitForLauncherCondition(
String message,
- Runnable testThreadAction, Function<Launcher, Boolean> condition,
+ Runnable testThreadAction, Function<LAUNCHER_TYPE, Boolean> condition,
long timeout) {
if (!TestHelpers.isInLauncherProcess()) return;
Wait.atMost(message, () -> {
@@ -529,13 +536,23 @@
@Override
public void onReceive(Context context, Intent intent) {
+ Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, intent == null
+ ? "AbstractLauncherUiTest.onReceive(): inputted intent NULL"
+ : "AbstractLauncherUiTest.onReceive(): inputted intent NOT NULL");
mIntent = intent;
latch.countDown();
+ Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT,
+ "AbstractLauncherUiTest.onReceive() Countdown Latch started");
}
public Intent blockingGetIntent() throws InterruptedException {
- latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
+ Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT,
+ "AbstractLauncherUiTest.blockingGetIntent()");
+ assertTrue("Timed Out", latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS));
mTargetContext.unregisterReceiver(this);
+ Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, mIntent == null
+ ? "AbstractLauncherUiTest.onReceive(): mIntent NULL"
+ : "AbstractLauncherUiTest.onReceive(): mIntent NOT NULL");
return mIntent;
}
@@ -578,6 +595,17 @@
false /* newTask */);
}
+ /** Starts ExcludeFromRecentsTestActivity, which has excludeFromRecents="true". */
+ public static void startExcludeFromRecentsTestActivity() {
+ final String packageName = getAppPackageName();
+ final Intent intent = getInstrumentation().getContext().getPackageManager()
+ .getLaunchIntentForPackage(packageName);
+ intent.setComponent(new ComponentName(packageName,
+ "com.android.launcher3.testcomponent.ExcludeFromRecentsTestActivity"));
+ startIntent(intent, By.pkg(packageName).text("ExcludeFromRecentsTestActivity"),
+ false /* newTask */);
+ }
+
private static void startIntent(Intent intent, BySelector selector, boolean newTask) {
intent.addCategory(Intent.CATEGORY_LAUNCHER);
if (newTask) {
@@ -621,7 +649,7 @@
"Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT);
}
- protected boolean isInLaunchedApp(Launcher launcher) {
+ protected boolean isInLaunchedApp(LAUNCHER_TYPE launcher) {
return launcher == null || !launcher.hasBeenResumed();
}
@@ -631,11 +659,11 @@
launcher -> launcher.getStateManager().getState() == state.get());
}
- protected int getAllAppsScroll(Launcher launcher) {
+ protected int getAllAppsScroll(LAUNCHER_TYPE launcher) {
return launcher.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset();
}
- protected void onLauncherActivityClose(Launcher launcher) {
+ protected void onLauncherActivityClose(LAUNCHER_TYPE launcher) {
}
protected HomeAppIcon createShortcutInCenterIfNotExist(String name) {
@@ -663,4 +691,19 @@
}
return homeAppIcon;
}
+
+ protected void commitTransactionAndLoadHome(FavoriteItemsTransaction transaction) {
+ transaction.commit();
+
+ // Launch the home activity
+ UiDevice.getInstance(getInstrumentation()).pressHome();
+ mLauncher.waitForLauncherInitialized();
+ }
+
+ /** Clears all recent tasks */
+ protected void clearAllRecentTasks() {
+ if (!mLauncher.getRecentTasks().isEmpty()) {
+ mLauncher.goHome().switchToOverview().dismissAllTasks();
+ }
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 5ef63da..e5c5c19 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -3,8 +3,9 @@
import android.util.Log;
import android.view.Surface;
+import com.android.launcher3.Launcher;
import com.android.launcher3.tapl.TestHelpers;
-import com.android.launcher3.util.rule.TestStabilityRule;
+import com.android.launcher3.util.rule.FailureWatcher;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -14,11 +15,12 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.Objects;
import java.util.concurrent.TimeUnit;
-public class PortraitLandscapeRunner implements TestRule {
+public class PortraitLandscapeRunner<LAUNCHER_TYPE extends Launcher> implements TestRule {
private static final String TAG = "PortraitLandscapeRunner";
- private AbstractLauncherUiTest mTest;
+ private AbstractLauncherUiTest<LAUNCHER_TYPE> mTest;
// Annotation for tests that need to be run in portrait and landscape modes.
@Retention(RetentionPolicy.RUNTIME)
@@ -26,18 +28,14 @@
public @interface PortraitLandscape {
}
- public PortraitLandscapeRunner(AbstractLauncherUiTest test) {
+ public PortraitLandscapeRunner(AbstractLauncherUiTest<LAUNCHER_TYPE> test) {
mTest = test;
}
@Override
public Statement apply(Statement base, Description description) {
if (!TestHelpers.isInLauncherProcess()
- || description.getAnnotation(PortraitLandscape.class) == null
- // If running in presubmit, don't run in both orientations.
- // It's important to keep presubmits fast even if we will occasionally miss
- // regressions in presubmit.
- || TestStabilityRule.isPresubmit()) {
+ || description.getAnnotation(PortraitLandscape.class) == null) {
return base;
}
@@ -45,17 +43,23 @@
@Override
public void evaluate() throws Throwable {
try {
- // we expect to begin unlocked...
- AbstractLauncherUiTest.verifyKeyguardInvisible();
+ try {
+ // we expect to begin unlocked...
+ AbstractLauncherUiTest.verifyKeyguardInvisible();
- mTest.mDevice.pressHome();
- mTest.waitForLauncherCondition("Launcher activity wasn't created",
- launcher -> launcher != null,
- TimeUnit.SECONDS.toMillis(20));
+ mTest.mDevice.pressHome();
+ mTest.waitForLauncherCondition("Launcher activity wasn't created",
+ Objects::nonNull,
+ TimeUnit.SECONDS.toMillis(20));
- mTest.executeOnLauncher(launcher ->
- launcher.getRotationHelper().forceAllowRotationForTesting(
- true));
+ mTest.executeOnLauncher(launcher ->
+ launcher.getRotationHelper().forceAllowRotationForTesting(
+ true));
+
+ } catch (Throwable e) {
+ FailureWatcher.onError(mTest.mLauncher, description);
+ throw e;
+ }
evaluateInPortrait();
evaluateInLandscape();
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
index 4eae7e1..342eedf 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
@@ -21,13 +21,17 @@
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;
import org.junit.runner.RunWith;
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplTestsLauncher3Test extends AbstractLauncherUiTest {
+public class TaplTestsLauncher3Test extends AbstractLauncherUiTest<Launcher> {
+ @ScreenRecord // b/322823478
@Test
public void testDevicePressMenu() throws Exception {
mDevice.pressMenu();
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index a9947a0..b2e413d 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -35,6 +35,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
@@ -58,7 +59,7 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplWorkProfileTest extends AbstractLauncherUiTest {
+public class TaplWorkProfileTest extends AbstractLauncherUiTest<Launcher> {
private static final int WORK_PAGE = ActivityAllAppsContainerView.AdapterHolder.WORK;
@@ -74,7 +75,6 @@
String output =
mDevice.executeShellCommand(
"pm create-user --profileOf 0 --managed TestProfile");
- // b/203817455
updateWorkProfileSetupSuccessful("pm create-user", output);
String[] tokens = output.split("\\s+");
@@ -113,7 +113,7 @@
mLauncher.runToState(
() -> {
try {
- mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+ mDevice.executeShellCommand("pm remove-user --wait " + mProfileUserId);
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -195,6 +195,7 @@
}
+ @ScreenRecord // b/322823478
@Test
public void testEdu() {
assumeTrue(mWorkProfileSetupSuccessful);
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java
index 7aa26a1..e6e02b4 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java
@@ -52,7 +52,7 @@
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest {
+public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest<Launcher> {
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
@@ -87,7 +87,7 @@
* @param acceptConfig accept the config activity
*/
private void runTest(boolean acceptConfig) throws Throwable {
- new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
+ commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
// Drag widget to homescreen
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index 5ca5ba5..9b184ae 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -25,13 +25,13 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
+import com.android.launcher3.Launcher;
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
import com.android.launcher3.tapl.Widget;
import com.android.launcher3.tapl.WidgetResizeFrame;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -46,17 +46,16 @@
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplAddWidgetTest extends AbstractLauncherUiTest {
+public class TaplAddWidgetTest extends AbstractLauncherUiTest<Launcher> {
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
@Test
@PortraitLandscape
- @ScreenRecord // b/316910614
public void testDragIcon() throws Throwable {
mLauncher.enableDebugTracing(); // b/289161193
- new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
+ commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
@@ -85,14 +84,13 @@
* A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
* Custom shortcuts are replaced by deep shortcuts after api 25.
*/
- @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
@Test
@PortraitLandscape
public void testDragCustomShortcut() throws Throwable {
// TODO(b/322820039): Enable test for tablets - the picker UI has changed and test needs to
// be updated to look for appropriate UI elements.
Assume.assumeFalse(mLauncher.isTablet());
- new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
+ commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
mLauncher.getWorkspace().openAllWidgets()
.getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
@@ -104,11 +102,11 @@
/**
* Test dragging a widget to the workspace and resize it.
*/
+ @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/316910614
@PlatinumTest(focusArea = "launcher")
@Test
- @ScreenRecord // b/316910614
public void testResizeWidget() throws Throwable {
- new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
+ commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java
index 6aa746d..28d1faa 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java
@@ -43,6 +43,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
@@ -76,7 +77,7 @@
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplBindWidgetTest extends AbstractLauncherUiTest {
+public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
@@ -251,22 +252,20 @@
private void addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus) {
item.restoreStatus = restoreStatus;
item.screenId = FIRST_SCREEN_ID;
- new FavoriteItemsTransaction(mTargetContext)
- .addItem(() -> item)
- .commitAndLoadHome(mLauncher);
+ commitTransactionAndLoadHome(
+ new FavoriteItemsTransaction(mTargetContext).addItem(() -> item));
}
private LauncherAppWidgetProviderInfo addWidgetToScreen(boolean hasConfigureScreen,
boolean bindWidget, Consumer<LauncherAppWidgetInfo> itemOverride) {
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(hasConfigureScreen);
- new FavoriteItemsTransaction(mTargetContext)
+ commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)
.addItem(() -> {
LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, bindWidget);
item.screenId = FIRST_SCREEN_ID;
itemOverride.accept(item);
return item;
- })
- .commitAndLoadHome(mLauncher);
+ }));
return info;
}
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java
index 7dba728..74047f0 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java
@@ -59,7 +59,7 @@
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplRequestPinItemTest extends AbstractLauncherUiTest {
+public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
@@ -128,7 +128,7 @@
private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
Intent... commandIntents) throws Throwable {
- new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
+ commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
// Open Pin item activity
BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java b/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
index 1cd8c88..19c5850 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
@@ -39,7 +39,7 @@
*/
@MediumTest
@RunWith(AndroidJUnit4.class)
-public class TaplWidgetPickerTest extends AbstractLauncherUiTest {
+public class TaplWidgetPickerTest extends AbstractLauncherUiTest<Launcher> {
private WidgetsRecyclerView getWidgetsView(Launcher launcher) {
return WidgetsFullSheet.getWidgetsView(launcher);
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
index c60ee8c..d653317 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
@@ -31,6 +31,7 @@
import androidx.test.filters.LargeTest;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.tapl.HomeAllApps;
import com.android.launcher3.tapl.HomeAppIcon;
@@ -49,7 +50,7 @@
* Note running these tests will clear the workspace on the device.
*/
@LargeTest
-public class TaplThemeIconsTest extends AbstractLauncherUiTest {
+public class TaplThemeIconsTest extends AbstractLauncherUiTest<Launcher> {
private static final String APP_NAME = "IconThemedActivity";
private static final String SHORTCUT_NAME = "Shortcut 1";
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index 43fc8ff..e92d641 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -20,6 +20,8 @@
import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.MESSAGES_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -37,6 +39,8 @@
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.rule.ScreenRecordRule;
+import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.After;
import org.junit.Before;
@@ -54,7 +58,7 @@
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplTwoPanelWorkspaceTest extends AbstractLauncherUiTest {
+public class TaplTwoPanelWorkspaceTest extends AbstractLauncherUiTest<Launcher> {
private AutoCloseable mLauncherLayout;
@@ -109,6 +113,8 @@
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/329935119
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/329935119
public void testSinglePageDragIconWhenMultiplePageScrollingIsPossible() {
Workspace workspace = mLauncher.getWorkspace();
@@ -240,6 +246,8 @@
});
}
+ @ScreenRecordRule.ScreenRecord // b/329935119
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/329935119
@Test
@PortraitLandscape
public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() {
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
index 4edeb42..490cff2 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -29,6 +29,7 @@
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import org.junit.After;
import org.junit.Before;
@@ -37,7 +38,7 @@
/**
* Test the basic interactions of the Workspace, adding pages, moving the pages and removing pages.
*/
-public class TaplWorkspaceTest extends AbstractLauncherUiTest {
+public class TaplWorkspaceTest extends AbstractLauncherUiTest<Launcher> {
private AutoCloseable mLauncherLayout;
@@ -67,6 +68,7 @@
* move between workspaces. After, make sure we can launch an app from the Workspace.
* @throws Exception if we can't set the defaults icons that will appear at the beginning.
*/
+ @ScreenRecord // b/331261431
@Test
public void testWorkspace() throws Exception {
// Set workspace that includes the chrome Activity app icon on the hotseat.
diff --git a/tests/src/com/android/launcher3/util/ItemInflaterTest.kt b/tests/src/com/android/launcher3/util/ItemInflaterTest.kt
index efad899..dae09bb 100644
--- a/tests/src/com/android/launcher3/util/ItemInflaterTest.kt
+++ b/tests/src/com/android/launcher3/util/ItemInflaterTest.kt
@@ -36,6 +36,7 @@
import com.android.launcher3.folder.FolderIcon
import com.android.launcher3.model.ModelWriter
import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.AppPairInfo
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
@@ -138,9 +139,9 @@
@Test
fun test_folder_inflated_on_UI() {
val itemInfo = FolderInfo()
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
val view =
MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
@@ -154,9 +155,9 @@
setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION)
val itemInfo = FolderInfo()
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
val view =
VIEW_PREINFLATION_EXECUTOR.submit(
@@ -170,10 +171,10 @@
@Test
fun test_app_pair_inflated_on_UI() {
- val itemInfo = FolderInfo()
+ val itemInfo = AppPairInfo()
itemInfo.itemType = ITEM_TYPE_APP_PAIR
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
val view =
MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
@@ -186,10 +187,10 @@
fun test_app_pair_inflated_on_BG() {
setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION)
- val itemInfo = FolderInfo()
+ val itemInfo = AppPairInfo()
itemInfo.itemType = ITEM_TYPE_APP_PAIR
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
val view =
VIEW_PREINFLATION_EXECUTOR.submit(
diff --git a/tests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
deleted file mode 100644
index b4d3ba8..0000000
--- a/tests/src/com/android/launcher3/util/TestResourceHelper.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util
-
-import android.content.Context
-import android.content.res.TypedArray
-import android.util.AttributeSet
-import com.android.launcher3.R
-import com.android.launcher3.tests.R as TestR
-import kotlin.IntArray
-
-class TestResourceHelper(private val context: Context, specsFileId: Int) :
- ResourceHelper(context, specsFileId) {
- override fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
- val clone =
- when {
- styleId.contentEquals(R.styleable.SizeSpec) -> TestR.styleable.SizeSpec
- styleId.contentEquals(R.styleable.WorkspaceSpec) -> TestR.styleable.WorkspaceSpec
- styleId.contentEquals(R.styleable.FolderSpec) -> TestR.styleable.FolderSpec
- styleId.contentEquals(R.styleable.AllAppsSpec) -> TestR.styleable.AllAppsSpec
- styleId.contentEquals(R.styleable.ResponsiveSpecGroup) ->
- TestR.styleable.ResponsiveSpecGroup
- else -> styleId.clone()
- }
-
- return context.obtainStyledAttributes(attrs, clone)
- }
-}
diff --git a/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java b/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java
new file mode 100644
index 0000000..702988c
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util.rule;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.ViewConfiguration;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class ExtendedLongPressTimeoutRule implements TestRule {
+
+ private static final String TAG = "ExtendedLongPressTimeoutRule";
+
+ private static final float LONG_PRESS_TIMEOUT_MULTIPLIER = 10f;
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ ContentResolver contentResolver = InstrumentationRegistry.getInstrumentation()
+ .getContext()
+ .getContentResolver();
+ int prevLongPressTimeout = Settings.Secure.getInt(
+ contentResolver,
+ Settings.Secure.LONG_PRESS_TIMEOUT,
+ ViewConfiguration.getLongPressTimeout());
+ int newLongPressTimeout =
+ (int) (prevLongPressTimeout * LONG_PRESS_TIMEOUT_MULTIPLIER);
+
+ try {
+ Log.d(TAG, "In try-block: Setting long press timeout from "
+ + prevLongPressTimeout + "ms to " + newLongPressTimeout + "ms");
+ Settings.Secure.putInt(
+ contentResolver,
+ Settings.Secure.LONG_PRESS_TIMEOUT,
+ (int) (prevLongPressTimeout * LONG_PRESS_TIMEOUT_MULTIPLIER));
+
+ base.evaluate();
+ } catch (Exception e) {
+ Log.e(TAG, "Error", e);
+ throw e;
+ } finally {
+ Log.d(TAG, "In finally-block: resetting long press timeout to "
+ + prevLongPressTimeout + "ms");
+ Settings.Secure.putInt(
+ contentResolver,
+ Settings.Secure.LONG_PRESS_TIMEOUT,
+ prevLongPressTimeout);
+ }
+ }
+ };
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 7fba33e..7bdc040 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -30,6 +30,8 @@
public class FailureWatcher extends TestWatcher {
private static final String TAG = "FailureWatcher";
private static boolean sSavedBugreport = false;
+ private static Description sDescriptionForLastSavedArtifacts;
+
private final LauncherInstrumentation mLauncher;
@NonNull
private final Supplier<ExportedData> mViewCaptureDataSupplier;
@@ -41,6 +43,18 @@
}
@Override
+ protected void starting(Description description) {
+ mLauncher.setOnFailure(() -> onError(mLauncher, description, mViewCaptureDataSupplier));
+ super.starting(description);
+ }
+
+ @Override
+ protected void finished(Description description) {
+ super.finished(description);
+ mLauncher.setOnFailure(null);
+ }
+
+ @Override
protected void succeeded(Description description) {
super.succeeded(description);
AbstractLauncherUiTest.checkDetectedLeaks(mLauncher);
@@ -70,7 +84,7 @@
@Override
protected void failed(Throwable e, Description description) {
- onError(mLauncher, description, e, mViewCaptureDataSupplier);
+ onError(mLauncher, description, mViewCaptureDataSupplier);
}
static File diagFile(Description description, String prefix, String ext) {
@@ -79,13 +93,18 @@
+ description.getMethodName() + "." + ext);
}
- public static void onError(LauncherInstrumentation launcher, Description description,
- Throwable e) {
- onError(launcher, description, e, null);
+ /** Action executed when an error condition is expected. Saves artifacts. */
+ public static void onError(LauncherInstrumentation launcher, Description description) {
+ onError(launcher, description, null);
}
private static void onError(LauncherInstrumentation launcher, Description description,
- Throwable e, @Nullable Supplier<ExportedData> viewCaptureDataSupplier) {
+ @Nullable Supplier<ExportedData> viewCaptureDataSupplier) {
+ if (description.equals(sDescriptionForLastSavedArtifacts)) {
+ // This test has already saved its artifacts.
+ return;
+ }
+ sDescriptionForLastSavedArtifacts = description;
final File sceenshot = diagFile(description, "TestScreenshot", "png");
final File hierarchy = diagFile(description, "Hierarchy", "zip");
@@ -114,7 +133,7 @@
Log.e(TAG, "Failed test " + description.getMethodName()
+ ",\nscreenshot will be saved to " + sceenshot
+ ",\nUI dump at: " + hierarchy
- + " (use go/web-hv to open the dump file)", e);
+ + " (use go/web-hv to open the dump file)");
final UiDevice device = launcher.getDevice();
device.takeScreenshot(sceenshot);
diff --git a/tests/src/com/android/launcher3/util/rule/SetPropRule.java b/tests/src/com/android/launcher3/util/rule/SetPropRule.java
new file mode 100644
index 0000000..74fec35
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/SetPropRule.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util.rule;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Test rule which executes a set prop command at the start of the test.
+ * This rule needs the property tag and property value so that value can be set to a tag.
+ */
+public class SetPropRule implements TestRule {
+ private static final String SETPROP_PREFIX = "setprop";
+ private static final String GETPROP_PREFIX = "getprop";
+ private static final String UNKNOWN = "UNKNOWN";
+ @NonNull private final String mPropTag;
+ @NonNull private final String mPropValue;
+
+ public SetPropRule(@NonNull String propTag, @NonNull String propValue) {
+ mPropTag = propTag.trim();
+ mPropValue = propValue.trim();
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ String getpropCmd = GETPROP_PREFIX + " " + mPropTag;
+ String initialValue = UiDevice.getInstance(getInstrumentation())
+ .executeShellCommand(getpropCmd);
+ if (TextUtils.isEmpty(initialValue.trim())) {
+ initialValue = UNKNOWN;
+ }
+ // setprop command always follows format : setprop <TAG> <value>
+ String revertSetPropCmd = SETPROP_PREFIX + " " + mPropTag + " " + initialValue;
+ String setPropCmd = SETPROP_PREFIX + " " + mPropTag + " " + mPropValue;
+ new ShellCommandRule(setPropCmd, revertSetPropCmd)
+ .apply(base, description).evaluate();
+ }
+ };
+ }
+
+ /**
+ * Enables "InputTransportPublisher" debug flag. This prints the key input events dispatched by
+ * the system server.
+ * adb shell setprop log.tag.InputTransportPublisher DEBUG
+ * See {@link com.android.cts.input.DebugInputRule} for more details.
+ */
+ public static SetPropRule createEnableInputTransportPublisherRule() {
+ return new SetPropRule("log.tag.InputTransportPublisher", "DEBUG");
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
index 08953fc..2219003 100644
--- a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
+++ b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.util.rule;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess;
@@ -39,7 +39,6 @@
* Test rule which executes a shell command at the start of the test.
*/
public class ShellCommandRule implements TestRule {
-
private final String mCmd;
private final String mRevertCommand;
private final boolean mCheckSuccess;
@@ -75,8 +74,7 @@
} finally {
if (mRevertCommand != null) {
final String revertResult = UiDevice.getInstance(
- getInstrumentation()).executeShellCommand(
- mRevertCommand);
+ getInstrumentation()).executeShellCommand(mRevertCommand);
if (mCheckSuccess) {
Assert.assertTrue(
"Failed command: " + mRevertCommand
diff --git a/tests/src/com/android/launcher3/util/rule/TestToPhoneFileCopier.kt b/tests/src/com/android/launcher3/util/rule/TestToPhoneFileCopier.kt
new file mode 100644
index 0000000..d3516d1
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/TestToPhoneFileCopier.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util.rule
+
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.File
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/** Copy a file from the tests assets folder to the phone. */
+class TestToPhoneFileCopier(
+ val src: String,
+ dest: String,
+ private val removeOnFinish: Boolean = false
+) : TestRule {
+
+ private val dstFile =
+ File(InstrumentationRegistry.getInstrumentation().targetContext.dataDir, dest)
+
+ fun getDst() = dstFile.absolutePath
+
+ fun before() =
+ dstFile.writeBytes(
+ InstrumentationRegistry.getInstrumentation().context.assets.open(src).readBytes()
+ )
+
+ fun after() {
+ if (removeOnFinish) {
+ dstFile.delete()
+ }
+ }
+
+ override fun apply(base: Statement, description: Description): Statement =
+ object : Statement() {
+ override fun evaluate() {
+ before()
+ try {
+ base.evaluate()
+ } finally {
+ after()
+ }
+ }
+ }
+}
diff --git a/tests/src_deviceless/com/android/launcher3/util/RobolectricDeviceRunner.kt b/tests/src_deviceless/com/android/launcher3/util/RobolectricDeviceRunner.kt
new file mode 100644
index 0000000..dc6d716
--- /dev/null
+++ b/tests/src_deviceless/com/android/launcher3/util/RobolectricDeviceRunner.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util
+
+import androidx.test.annotation.UiThreadTest
+import androidx.test.platform.app.InstrumentationRegistry
+import java.lang.reflect.Method
+import java.util.concurrent.atomic.AtomicReference
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.internal.bytecode.Sandbox
+import org.robolectric.util.ReflectionHelpers
+import org.robolectric.util.ReflectionHelpers.ClassParameter
+
+/** Runner which emulates the provided display before running the actual test */
+class RobolectricDeviceRunner(testClass: Class<*>?, private val deviceName: String?) :
+ RobolectricTestRunner(testClass) {
+
+ private val nameSuffix = deviceName?.let { "-$it" } ?: ""
+
+ override fun getName() = super.getName() + nameSuffix
+
+ override fun testName(method: FrameworkMethod) = super.testName(method) + nameSuffix
+
+ @Throws(Throwable::class)
+ override fun beforeTest(sandbox: Sandbox, method: FrameworkMethod, bootstrappedMethod: Method) {
+ super.beforeTest(sandbox, method, bootstrappedMethod)
+
+ deviceName ?: return
+
+ val emulator =
+ try {
+ ReflectionHelpers.loadClass(
+ bootstrappedMethod.declaringClass.classLoader,
+ DEVICE_EMULATOR
+ )
+ } catch (e: Exception) {
+ // Ignore, if the device emulator is not present
+ return
+ }
+ ReflectionHelpers.callStaticMethod<Any>(
+ emulator,
+ "updateDevice",
+ ClassParameter.from(String::class.java, deviceName)
+ )
+ }
+
+ override fun getHelperTestRunner(clazz: Class<*>) = MyHelperTestRunner(clazz)
+
+ class MyHelperTestRunner(clazz: Class<*>) : HelperTestRunner(clazz) {
+
+ override fun methodBlock(method: FrameworkMethod): Statement =
+ // this needs to be run in the test classLoader
+ ReflectionHelpers.callStaticMethod(
+ method.declaringClass.classLoader,
+ RobolectricDeviceRunner::class.qualifiedName,
+ "wrapUiThreadMethod",
+ ClassParameter.from(FrameworkMethod::class.java, method),
+ ClassParameter.from(Statement::class.java, super.methodBlock(method))
+ )
+ }
+
+ private class UiThreadStatement(val base: Statement) : Statement() {
+
+ override fun evaluate() {
+ val exceptionRef = AtomicReference<Throwable>()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ try {
+ base.evaluate()
+ } catch (throwable: Throwable) {
+ exceptionRef.set(throwable)
+ }
+ }
+ exceptionRef.get()?.let { throw it }
+ }
+ }
+
+ companion object {
+
+ private const val DEVICE_EMULATOR = "com.android.launcher3.util.RoboDeviceEmulator"
+
+ @JvmStatic
+ fun wrapUiThreadMethod(method: FrameworkMethod, base: Statement): Statement =
+ if (
+ method.method.isAnnotationPresent(UiThreadTest::class.java) ||
+ method.declaringClass.isAnnotationPresent(UiThreadTest::class.java)
+ ) {
+ UiThreadStatement(base)
+ } else {
+ base
+ }
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 9591891..71cd19d 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -56,11 +56,9 @@
private static final String BOTTOM_SHEET_RES_ID = "bottom_sheet_background";
private static final String FAST_SCROLLER_RES_ID = "fast_scroller";
-
- private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile(
- "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
"Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+ private static final String UNLOCK_BUTTON_VIEW_RES_ID = "ps_lock_unlock_button";
private final int mHeight;
private final int mIconHeight;
@@ -294,6 +292,10 @@
return mLauncher.waitForObjectInContainer(allAppsContainer, "apps_list_view");
}
+ protected UiObject2 getAllAppsHeader(UiObject2 allAppsContainer) {
+ return mLauncher.waitForObjectInContainer(allAppsContainer, "all_apps_header");
+ }
+
protected UiObject2 getSearchBox(UiObject2 allAppsContainer) {
return mLauncher.waitForObjectInContainer(allAppsContainer, "search_container_all_apps");
}
@@ -407,7 +409,6 @@
/** Presses the esc key to dismiss AllApps. */
public void dismissByEscKey() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
mLauncher.runToState(
() -> mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE),
@@ -420,6 +421,43 @@
}
}
+ /** Returns PredictionRow if present in view. */
+ @NonNull
+ public PredictionRow getPredictionRowView() {
+ final UiObject2 allAppsContainer = verifyActiveContainer();
+ final UiObject2 allAppsHeader = getAllAppsHeader(allAppsContainer);
+ return new PredictionRow(mLauncher, allAppsHeader);
+ }
+
+ /** Returns PrivateSpaceContainer if present in view. */
+ @NonNull
+ public PrivateSpaceContainer getPrivateSpaceUnlockedView() {
+ final UiObject2 allAppsContainer = verifyActiveContainer();
+ final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
+ return new PrivateSpaceContainer(mLauncher, appListRecycler, this, true);
+ }
+
+ /** Returns PrivateSpaceContainer in locked state, if present in view. */
+ @NonNull
+ public PrivateSpaceContainer getPrivateSpaceLockedView() {
+ final UiObject2 allAppsContainer = verifyActiveContainer();
+ final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
+ return new PrivateSpaceContainer(mLauncher, appListRecycler, this, false);
+ }
+
+ /**
+ * Toggles Lock/Unlock of Private Space, changing the All Apps Ui.
+ */
+ public void togglePrivateSpace() {
+ final UiObject2 allAppsContainer = verifyActiveContainer();
+ final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
+ UiObject2 unLockButtonView = mLauncher.waitForObjectInContainer(appListRecycler,
+ UNLOCK_BUTTON_VIEW_RES_ID);
+ mLauncher.waitForObjectEnabled(unLockButtonView, "Private Space Unlock Button");
+ mLauncher.assertTrue("PS Unlock Button is non-clickable", unLockButtonView.isClickable());
+ unLockButtonView.click();
+ }
+
protected abstract void verifyVisibleContainerOnDismiss();
/**
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index ad95ecf..27f6c16 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import android.graphics.Rect;
+import android.util.Log;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
@@ -45,9 +46,6 @@
*/
public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
protected static final String TASK_RES_ID = "task";
-
- private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile(
- "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
"Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
private static final Pattern EVENT_ENTER_DOWN = Pattern.compile(
@@ -386,23 +384,40 @@
}
protected boolean isActionsViewVisible() {
- if (!hasTasks() || isClearAllVisible()) {
+ boolean hasTasks = hasTasks();
+ if (!hasTasks || isClearAllVisible()) {
+ LauncherInstrumentation.log("Not expecting an actions bar:"
+ + (!hasTasks ? "no recent tasks" : "clear all button is visible"));
return false;
}
boolean isTablet = mLauncher.isTablet();
if (isTablet && mLauncher.isGridOnlyOverviewEnabled()) {
+ LauncherInstrumentation.log("Not expecting an actions bar: "
+ + "device is tablet with grid-only Overview");
return false;
}
OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask();
if (task == null) {
+ LauncherInstrumentation.log("Not expecting an actions bar: no focused task");
return false;
}
+ float centerOffset = Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX());
// In tablets, if focused task is not in center, overview actions aren't visible.
- if (isTablet && Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()) >= 1) {
+ if (isTablet && centerOffset >= 1) {
+ LauncherInstrumentation.log("Not expecting an actions bar: "
+ + "device is tablet and task is not centered; center offset by "
+ + centerOffset + "px");
return false;
}
- // Overview actions aren't visible for split screen tasks.
- return !task.isTaskSplit();
+ if (task.isTaskSplit() && (!mLauncher.isAppPairsEnabled() || !isTablet)) {
+ LauncherInstrumentation.log("Not expecting an actions bar: "
+ + "device is phone and task is split");
+ // Overview actions aren't visible for split screen tasks, except for save app pair
+ // button on tablets.
+ return false;
+ }
+ LauncherInstrumentation.log("Expecting an actions bar");
+ return true;
}
/**
@@ -410,7 +425,6 @@
*/
public Workspace dismissByEscKey() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
mLauncher.runToState(
() -> mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE),
@@ -429,7 +443,6 @@
*/
public LaunchedAppState launchFocusedTaskByEnterKey(@NonNull String expectedPackageName) {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ENTER_DOWN);
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ENTER_UP);
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
@@ -448,12 +461,30 @@
}
private void verifyActionsViewVisibility() {
+ // If no running tasks, no need to verify actions view visibility.
+ if (getTasks().isEmpty()) {
+ return;
+ }
+
+ boolean isTablet = mLauncher.isTablet();
+ OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask();
+
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to assert overview actions view visibility")) {
+ "want to assert overview actions view visibility="
+ + isActionsViewVisible()
+ + ", focused task is "
+ + (task == null ? "null" : (task.isTaskSplit() ? "split" : "not split"))
+ )) {
+
if (isActionsViewVisible()) {
- mLauncher.waitForOverviewObject("action_buttons");
+ if (task.isTaskSplit()) {
+ mLauncher.waitForOverviewObject("action_save_app_pair");
+ } else {
+ mLauncher.waitForOverviewObject("action_buttons");
+ }
} else {
mLauncher.waitUntilOverviewObjectGone("action_buttons");
+ mLauncher.waitUntilOverviewObjectGone("action_save_app_pair");
}
}
}
@@ -468,13 +499,18 @@
throw new IllegalStateException("Must be run on tablet device.");
}
final List<UiObject2> taskViews = getTasks();
- if (taskViews.size() == 0) {
+ if (!hasTasks()) {
+ LauncherInstrumentation.log("no recent tasks");
return null;
}
int focusedTaskHeight = mLauncher.getFocusedTaskHeightForTablet();
for (UiObject2 task : taskViews) {
OverviewTask overviewTask = new OverviewTask(mLauncher, task, this);
+ LauncherInstrumentation.log("checking task height ("
+ + overviewTask.getVisibleHeight()
+ + ") against defined focused task height ("
+ + focusedTaskHeight + ")");
if (overviewTask.getVisibleHeight() == focusedTaskHeight) {
return overviewTask;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
index 5ef82ca..7ff55fe 100644
--- a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
@@ -37,15 +37,9 @@
private static final Pattern EVENT_ALT_TAB_UP = Pattern.compile(
"KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB"
+ ".*?metaState=META_ALT_ON");
- private static final Pattern EVENT_ALT_SHIFT_TAB_DOWN = Pattern.compile(
- "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_TAB"
- + ".*?metaState=META_ALT_ON|META_SHIFT_ON");
private static final Pattern EVENT_ALT_SHIFT_TAB_UP = Pattern.compile(
"KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB"
+ ".*?metaState=META_ALT_ON|META_SHIFT_ON");
- private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile(
- "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN"
- + ".*?keyCode=KEYCODE_ESCAPE.*?metaState=META_ALT_ON");
private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
"KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP"
+ ".*?keyCode=KEYCODE_ESCAPE.*?metaState=META_ALT_ON");
@@ -87,8 +81,6 @@
"want to move keyboard quick switch focus forward");
LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
-
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_DOWN);
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_UP);
mLauncher.assertTrue("Failed to press alt+tab",
mLauncher.getDevice().pressKeyCode(
@@ -122,7 +114,6 @@
LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_DOWN);
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_UP);
mLauncher.assertTrue("Failed to press alt+shift+tab",
mLauncher.getDevice().pressKeyCode(
@@ -150,7 +141,6 @@
LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
mLauncher.assertTrue("Failed to press alt+tab",
mLauncher.getDevice().pressKeyCode(
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index b5414b7..200f2ff 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -24,9 +24,7 @@
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_SHELL_DRAG_READY;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_STASHED_TASKBAR_SCALE;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_TASKBAR_FROM_NAV_THRESHOLD;
-import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import android.graphics.Point;
import android.graphics.Rect;
@@ -141,8 +139,6 @@
return new Taskbar(mLauncher);
} finally {
- testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
- "swipeUpToUnstashTaskbar: completed gesture");
mLauncher.getTestInfo(REQUEST_DISABLE_BLOCK_TIMEOUT);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 053b360..f02a0c2 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -24,16 +24,15 @@
import static android.view.MotionEvent.ACTION_SCROLL;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
+import static android.view.Surface.ROTATION_90;
import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID;
import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_GET_SPLIT_SELECTION_ACTIVE;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS;
-import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import android.app.ActivityManager;
import android.app.Instrumentation;
@@ -109,14 +108,11 @@
public final class LauncherInstrumentation {
private static final String TAG = "Tapl";
- private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 15;
+ private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 5;
private static final int GESTURE_STEP_MS = 16;
static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
static final Pattern EVENT_START = Pattern.compile("start:");
-
- private static final Pattern EVENT_KEY_BACK_DOWN =
- getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK");
private static final Pattern EVENT_KEY_BACK_UP =
getKeyEventPattern("ACTION_UP", "KEYCODE_BACK");
private static final Pattern EVENT_ON_BACK_INVOKED = Pattern.compile("onBackInvoked");
@@ -208,6 +204,7 @@
private Runnable mTestAnomalyChecker;
private boolean mCheckEventsForSuccessfulGestures = false;
+ private Runnable mOnFailure;
private Runnable mOnLauncherCrashed;
private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
@@ -346,6 +343,11 @@
mCheckEventsForSuccessfulGestures = true;
}
+ /** Sets a runnable that will be invoked upon assertion failures. */
+ public void setOnFailure(Runnable onFailure) {
+ mOnFailure = onFailure;
+ }
+
public void setOnLauncherCrashed(Runnable onLauncherCrashed) {
mOnLauncherCrashed = onLauncherCrashed;
}
@@ -405,6 +407,11 @@
.getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ private boolean isPredictiveBackSwipeEnabled() {
+ return getTestInfo(TestProtocol.REQUEST_IS_PREDICTIVE_BACK_SWIPE_ENABLED)
+ .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
public boolean isTaskbarNavbarUnificationEnabled() {
return getTestInfo(TestProtocol.REQUEST_ENABLE_TASKBAR_NAVBAR_UNIFICATION)
.getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -472,12 +479,25 @@
logShellCommand(cmd);
}
+ /**
+ * Retrieves a resource value from context that defines if nav bar can change position or if it
+ * is fixed position regardless of device orientation.
+ */
+ private boolean getNavBarCanMove() {
+ final Context baseContext = mInstrumentation.getTargetContext();
+ try {
+ final Context ctx = getLauncherContext(baseContext);
+ return getNavBarCanMove(ctx);
+ } catch (Exception e) {
+ fail(e.toString());
+ }
+ return false;
+ }
+
public NavigationModel getNavigationModel() {
final Context baseContext = mInstrumentation.getTargetContext();
try {
- // Workaround, use constructed context because both the instrumentation context and the
- // app context are not constructed with resources that take overlays into account
- final Context ctx = baseContext.createPackageContext(getLauncherPackageName(), 0);
+ final Context ctx = getLauncherContext(baseContext);
for (int i = 0; i < 100; ++i) {
final int currentInteractionMode = getCurrentInteractionMode(ctx);
final NavigationModel model = getNavigationModel(currentInteractionMode);
@@ -609,6 +629,7 @@
final String systemAnomalyMessage =
getSystemAnomalyMessage(ignoreNavmodeChangeStates, ignoreOnlySystemUiViews);
if (systemAnomalyMessage != null) {
+ if (mOnFailure != null) mOnFailure.run();
Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
"http://go/tapl : Tests are broken by a non-Launcher system error: "
+ systemAnomalyMessage, false)));
@@ -690,7 +711,7 @@
final LogEventChecker eventChecker = mEventChecker;
mEventChecker = null;
if (checkEvents) {
- final String eventMismatch = eventChecker.verify(0, false);
+ final String eventMismatch = eventChecker.verify(0);
if (eventMismatch != null) {
message = message + ";\n" + eventMismatch;
}
@@ -728,6 +749,7 @@
void fail(String message) {
checkForAnomaly();
+ if (mOnFailure != null) mOnFailure.run();
Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
"http://go/tapl test failure: " + message + ";\nContext: " + getContextDescription()
+ "; now visible state is " + getVisibleStateMessage(), true)));
@@ -752,7 +774,7 @@
fail(message + ". " + "Actual: " + actual);
}
- private void assertEquals(String message, int expected, int actual) {
+ void assertEquals(String message, int expected, int actual) {
if (expected != actual) {
fail(message + " expected: " + expected + " but was: " + actual);
}
@@ -1099,19 +1121,31 @@
/**
* Goes to home from immersive fullscreen app by first swiping up to bring navbar, and then
* performing {@code goHome()} action.
- * Currently only supports gesture navigation mode.
*
* @return the Workspace object.
*/
public Workspace goHomeFromImmersiveFullscreenApp() {
- assertTrue("expected gesture navigation mode",
- getNavigationModel() == NavigationModel.ZERO_BUTTON);
- final Point displaySize = getRealDisplaySize();
- linearGesture(
- displaySize.x / 2, displaySize.y - 1,
- displaySize.x / 2, 0,
- ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
- false, GestureScope.EXPECT_PILFER);
+ final boolean navBarCanMove = getNavBarCanMove();
+ if (getNavigationModel() == NavigationModel.ZERO_BUTTON || !navBarCanMove) {
+ // in gesture nav we can swipe up at the bottom to bring the navbar handle
+ final Point displaySize = getRealDisplaySize();
+ linearGesture(
+ displaySize.x / 2, displaySize.y - 1,
+ displaySize.x / 2, 0,
+ ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
+ false, GestureScope.EXPECT_PILFER);
+ } else {
+ // in 3 button nav we swipe up on the side edge of the screen to bring the navbar
+ final boolean rotated90degrees = mDevice.getDisplayRotation() == ROTATION_90;
+ final Point displaySize = getRealDisplaySize();
+ final int startX = rotated90degrees ? displaySize.x : 0;
+ final int endX = rotated90degrees ? 0 : displaySize.x;
+ linearGesture(
+ startX, displaySize.y / 2,
+ endX, displaySize.y / 2,
+ ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
+ false, GestureScope.EXPECT_PILFER);
+ }
return goHome();
}
@@ -1214,11 +1248,9 @@
waitForNavigationUiObject("back").click();
}
if (launcherVisible) {
- if (InstrumentationRegistry.getTargetContext().getApplicationInfo()
- .isOnBackInvokedCallbackEnabled()) {
+ if (isPredictiveBackSwipeEnabled()) {
expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ON_BACK_INVOKED);
} else {
- expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN);
expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP);
}
}
@@ -1919,17 +1951,21 @@
}
private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
- float x, float y, int source) {
+ float x, float y, int source, int toolType) {
return MotionEvent.obtain(downTime, eventTime, action, 1,
- new MotionEvent.PointerProperties[]{getPointerProperties(0)},
+ new MotionEvent.PointerProperties[]{getPointerProperties(0, toolType)},
new MotionEvent.PointerCoords[]{getPointerCoords(x, y)},
0, 0, 1.0f, 1.0f, 0, 0, source, 0);
}
private static MotionEvent.PointerProperties getPointerProperties(int pointerId) {
+ return getPointerProperties(pointerId, Configurator.getInstance().getToolType());
+ }
+
+ private static MotionEvent.PointerProperties getPointerProperties(int pointerId, int toolType) {
MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
properties.id = pointerId;
- properties.toolType = Configurator.getInstance().getToolType();
+ properties.toolType = toolType;
return properties;
}
@@ -1952,6 +1988,11 @@
TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ boolean isAppPairsEnabled() {
+ return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS).getBoolean(
+ TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
public void sendPointer(long downTime, long currentTime, int action, Point point,
GestureScope gestureScope) {
sendPointer(downTime, currentTime, action, point, gestureScope,
@@ -1970,6 +2011,19 @@
public void sendPointer(long downTime, long currentTime, int action, Point point,
GestureScope gestureScope, int source, boolean isRightClick) {
+ sendPointer(
+ downTime,
+ currentTime,
+ action,
+ point,
+ gestureScope,
+ source,
+ isRightClick,
+ Configurator.getInstance().getToolType());
+ }
+
+ public void sendPointer(long downTime, long currentTime, int action, Point point,
+ GestureScope gestureScope, int source, boolean isRightClick, int toolType) {
final boolean hasTIS = hasTIS();
int pointerCount = mPointerCount;
@@ -2004,13 +2058,13 @@
? getTrackpadMotionEvent(
downTime, currentTime, action, point.x, point.y, pointerCount,
mTrackpadGestureType)
- : getMotionEvent(downTime, currentTime, action, point.x, point.y, source);
+ : getMotionEvent(downTime, currentTime, action, point.x, point.y, source, toolType);
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.setButtonState(event.getButtonState() | MotionEvent.BUTTON_SECONDARY);
}
injectEvent(event);
}
@@ -2104,20 +2158,31 @@
return getSystemIntegerRes(context, "config_navBarInteractionMode");
}
+ /**
+ * Retrieve the resource value that defines if nav bar can moved or if it is fixed position.
+ */
+ private static boolean getNavBarCanMove(Context context) {
+ return getSystemBooleanRes(context, "config_navBarCanMove");
+ }
+
@NonNull
UiObject2 clickAndGet(
@NonNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent) {
final Point targetCenter = target.getVisibleCenter();
final long downTime = SystemClock.uptimeMillis();
+ // Use stylus secondary button press to prevent using the exteded long press timeout rule
+ // unnecessarily
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
- GestureScope.DONT_EXPECT_PILFER);
+ 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, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
- GestureScope.DONT_EXPECT_PILFER);
+ GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
+ /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
}
}
@@ -2140,6 +2205,18 @@
}
}
+ private static boolean getSystemBooleanRes(Context context, String resName) {
+ Resources res = context.getResources();
+ int resId = res.getIdentifier(resName, "bool", "android");
+
+ if (resId != 0) {
+ return res.getBoolean(resId);
+ } else {
+ Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+ return false;
+ }
+ }
+
private static int getSystemIntegerRes(Context context, String resName) {
Resources res = context.getResources();
int resId = res.getIdentifier(resName, "integer", "android");
@@ -2241,6 +2318,14 @@
getTestInfo(TestProtocol.REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED);
}
+ public void injectFakeTrackpad() {
+ getTestInfo(TestProtocol.REQUEST_INJECT_FAKE_TRACKPAD);
+ }
+
+ public void ejectFakeTrackpad() {
+ getTestInfo(TestProtocol.REQUEST_EJECT_FAKE_TRACKPAD);
+ }
+
/** Blocks the taskbar from automatically stashing based on time. */
public void enableBlockTimeout(boolean enable) {
getTestInfo(enable
@@ -2329,13 +2414,9 @@
}
if (mEventChecker != null) {
- testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "eventsCheck: mEventChecker exists");
mEventChecker = null;
if (mCheckEventsForSuccessfulGestures) {
- final String message = eventChecker.verify(WAIT_TIME_MS, true);
- testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
- "mCheckEventsForSuccessfulGestures = true | eventsCheck: message="
- + message);
+ final String message = eventChecker.verify(WAIT_TIME_MS);
if (message != null) {
dumpDiagnostics(message);
checkForAnomaly();
@@ -2405,6 +2486,13 @@
return Math.max(topRadius, bottomRadius) + tmpBuffer;
}
+ private Context getLauncherContext(Context baseContext)
+ throws PackageManager.NameNotFoundException {
+ // Workaround, use constructed context because both the instrumentation context and the
+ // app context are not constructed with resources that take overlays into account
+ return baseContext.createPackageContext(getLauncherPackageName(), 0);
+ }
+
private static boolean supportsRoundedCornersOnWindows(Resources resources) {
return ResourceUtils.getBoolByName(
"config_supportsRoundedCornersOnWindows", resources, false);
@@ -2447,7 +2535,7 @@
int bottomBound = Math.min(
containerBounds.bottom,
getRealDisplaySize().y - systemGestureRegion.bottom);
- int y = (bottomBound - containerBounds.top) / 2;
+ int y = (bottomBound + containerBounds.top) / 2;
// Do not tap in the status bar.
y = Math.max(y, systemGestureRegion.top);
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 70d63bd..055a357 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -15,12 +15,6 @@
*/
package com.android.launcher3.tapl;
-import static com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_MAIN;
-import static com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_PILFER;
-import static com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_TIS;
-import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
-
import android.os.SystemClock;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -89,26 +83,11 @@
mLauncher.getTestInfo(TestProtocol.REQUEST_STOP_EVENT_LOGGING);
}
- String verify(long waitForExpectedCountMs, boolean successfulGesture) {
- testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "LogEventChecker.java - verify");
+ String verify(long waitForExpectedCountMs) {
final ListMap<String> actualEvents = finishSync(waitForExpectedCountMs);
if (actualEvents == null) return "null event sequences because launcher likely died";
- final String lowLevelDiags = lowLevelMismatchDiagnostics(actualEvents);
- // If we have a sequence mismatch for a successful gesture, we want to provide all low-level
- // details.
- if (successfulGesture) {
- return lowLevelDiags;
- }
-
- final String sequenceMismatchInEnglish = highLevelMismatchDiagnostics(actualEvents);
-
- if (sequenceMismatchInEnglish != null) {
- LauncherInstrumentation.log(lowLevelDiags);
- return "Hint: " + sequenceMismatchInEnglish;
- } else {
- return lowLevelDiags;
- }
+ return lowLevelMismatchDiagnostics(actualEvents);
}
private String lowLevelMismatchDiagnostics(ListMap<String> actualEvents) {
@@ -143,42 +122,6 @@
return hasMismatches ? "Mismatching events: " + sb.toString() : null;
}
- private String highLevelMismatchDiagnostics(ListMap<String> actualEvents) {
- if (!mExpectedEvents.getNonNull(SEQUENCE_TIS).isEmpty()
- && actualEvents.getNonNull(SEQUENCE_TIS).isEmpty()) {
- return "TouchInteractionService didn't receive any of the touch events sent by the "
- + "test";
- }
- if (getMismatchPosition(mExpectedEvents.getNonNull(SEQUENCE_TIS),
- actualEvents.getNonNull(SEQUENCE_TIS)) != -1) {
- // If TIS has a mismatch that we can't convert to high-level diags, don't convert
- // other sequences either.
- return null;
- }
-
- if (mExpectedEvents.getNonNull(SEQUENCE_PILFER).size() == 1
- && actualEvents.getNonNull(SEQUENCE_PILFER).isEmpty()) {
- return "Launcher didn't detect the navigation gesture sent by the test";
- }
- if (mExpectedEvents.getNonNull(SEQUENCE_PILFER).isEmpty()
- && actualEvents.getNonNull(SEQUENCE_PILFER).size() == 1) {
- return "Launcher detected a navigation gesture, but the test didn't send one";
- }
- if (getMismatchPosition(mExpectedEvents.getNonNull(SEQUENCE_PILFER),
- actualEvents.getNonNull(SEQUENCE_PILFER)) != -1) {
- // If Pilfer has a mismatch that we can't convert to high-level diags, don't analyze
- // other sequences.
- return null;
- }
-
- if (!mExpectedEvents.getNonNull(SEQUENCE_MAIN).isEmpty()
- && actualEvents.getNonNull(SEQUENCE_MAIN).isEmpty()) {
- return "None of the touch or keyboard events sent by the test was received by "
- + "Launcher's main thread";
- }
- return null;
- }
-
// If the list of actual events matches the list of expected events, returns -1, otherwise
// the position of the mismatch.
private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index afe5722..6f420af 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -23,6 +23,7 @@
import android.graphics.Rect;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiObject2;
@@ -256,6 +257,23 @@
}
/**
+ * Returns whether the given String is contained in this Task's contentDescription. Also returns
+ * true if both Strings are null.
+ *
+ * TODO(b/326565120): remove Nullable support once the bug causing it to be null is fixed.
+ */
+ public boolean containsContentDescription(@Nullable String expected) {
+ String actual = mTask.getContentDescription();
+ if (actual == null && expected == null) {
+ return true;
+ }
+ if (actual == null || expected == null) {
+ return false;
+ }
+ return actual.contains(expected);
+ }
+
+ /**
* Enum used to specify which task is retrieved when it is a split task.
*/
public enum OverviewSplitTask {
diff --git a/tests/tapl/com/android/launcher3/tapl/PredictionRow.java b/tests/tapl/com/android/launcher3/tapl/PredictionRow.java
new file mode 100644
index 0000000..11f71fd
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/PredictionRow.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+/** View containing prediction app icons */
+public class PredictionRow {
+
+ private static final String PREDICTION_ROW_ID = "prediction_row";
+ private static final String PREDICTION_APP_ID = "icon";
+ private final LauncherInstrumentation mLauncher;
+ private final UiObject2 mAllAppsHeader;
+ private final UiObject2 mPredictionRow;
+
+ PredictionRow(LauncherInstrumentation launcherInstrumentation,
+ UiObject2 allAppsHeader) {
+ mLauncher = launcherInstrumentation;
+ mAllAppsHeader = allAppsHeader;
+ mPredictionRow = mLauncher.waitForObjectInContainer(mAllAppsHeader,
+ PREDICTION_ROW_ID);
+ verifyAppsPresentInsidePredictionRow();
+ verifyPredictionRowAppsCount();
+ }
+
+ /** Verify that one app is present in prediction row view. */
+ private void verifyAppsPresentInsidePredictionRow() {
+ mLauncher.waitForObjectInContainer(mPredictionRow,
+ PREDICTION_APP_ID);
+ }
+
+ /** Verify that prediction row apps count is same as launcher apps column count. */
+ private void verifyPredictionRowAppsCount() {
+ mLauncher.assertEquals("PredictionRow app count mismatch", mLauncher.getNumAllAppsColumns(),
+ getPredictionRowAppsCount());
+ }
+
+ /**
+ * Returns an app icon found in the prediction row. This fails if any icon is not
+ * found.
+ */
+ @NonNull
+ private HomeAppIcon getAnyAppIcon() {
+ return new AllAppsAppIcon(mLauncher,
+ mPredictionRow.findObject(AppIcon.getAnyAppIconSelector()));
+ }
+
+ /**
+ * Returns the size of prediction row apps count.
+ */
+ private int getPredictionRowAppsCount() {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to get all prediction row icons")) {
+ return mPredictionRow.findObjects(AppIcon.getAnyAppIconSelector()).size();
+ }
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java
new file mode 100644
index 0000000..a2814f0
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java
@@ -0,0 +1,94 @@
+/*
+ * 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.tapl;
+
+import android.graphics.Point;
+
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * View containing Private Space elements.
+ */
+public class PrivateSpaceContainer {
+ private static final String PS_HEADER_RES_ID = "ps_header_layout";
+ private static final String INSTALL_APP_TITLE = "Install";
+ private static final String DIVIDER_RES_ID = "private_space_divider";
+
+ private final LauncherInstrumentation mLauncher;
+ private final UiObject2 mAppListRecycler;
+ private final AllApps mAppList;
+ private final boolean mPrivateSpaceEnabled;
+
+ PrivateSpaceContainer(LauncherInstrumentation launcherInstrumentation,
+ UiObject2 appListRecycler, AllApps appList, boolean privateSpaceEnabled) {
+ mLauncher = launcherInstrumentation;
+ mAppListRecycler = appListRecycler;
+ mAppList = appList;
+ mPrivateSpaceEnabled = privateSpaceEnabled;
+
+ if (mPrivateSpaceEnabled) {
+ verifyHeaderIsPresent();
+ verifyInstallAppButtonIsPresent();
+ verifyDividerIsPresent();
+ } else {
+ verifyHeaderIsPresent();
+ verifyInstallAppButtonIsNotPresent();
+ verifyDividerIsNotPresent();
+ }
+ }
+
+ // Assert PS Header is in view.
+ // Assert PS header has the correct elements.
+ private void verifyHeaderIsPresent() {
+ final UiObject2 psHeader = mLauncher.waitForObjectInContainer(mAppListRecycler,
+ PS_HEADER_RES_ID);
+ new PrivateSpaceHeader(mLauncher, psHeader, mPrivateSpaceEnabled);
+ }
+
+
+ // Assert Install App Item is present in view.
+ private void verifyInstallAppButtonIsPresent() {
+ mAppList.getAppIcon(INSTALL_APP_TITLE);
+ }
+
+ // Assert Install App Item is not present in view.
+ private void verifyInstallAppButtonIsNotPresent() {
+ mLauncher.waitUntilLauncherObjectGone(DIVIDER_RES_ID);
+ }
+
+ // Assert Sys App Divider is present in view.
+ private void verifyDividerIsPresent() {
+ mLauncher.waitForObjectInContainer(mAppListRecycler, DIVIDER_RES_ID);
+ }
+
+ // Assert Sys App Divider is not present in view.
+ private void verifyDividerIsNotPresent() {
+ mLauncher.waitUntilLauncherObjectGone(DIVIDER_RES_ID);
+ }
+
+ /**
+ * Verifies that a user installed app is present above the divider.
+ */
+ public void verifyInstalledAppIsPresent(String appName) {
+ AppIcon appIcon = mAppList.getAppIcon(appName);
+ final Point iconCenter = appIcon.mObject.getVisibleCenter();
+ UiObject2 divider = mLauncher.waitForObjectInContainer(mAppListRecycler, DIVIDER_RES_ID);
+ final Point dividerCenter = divider.getVisibleCenter();
+ mLauncher.assertTrue("Installed App: " + appName + " is not above the divider",
+ iconCenter.y < dividerCenter.y);
+ }
+}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/PrivateSpaceHeader.java b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceHeader.java
new file mode 100644
index 0000000..cc64aae
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceHeader.java
@@ -0,0 +1,96 @@
+/*
+ * 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * View containing Private Space Header.
+ */
+public class PrivateSpaceHeader {
+ private static final String PS_HEADER_TEXT_RES_ID = "ps_container_header";
+ private static final String SETTINGS_BUTTON_RES_ID = "ps_settings_button";
+ private static final String UNLOCK_BUTTON_VIEW_RES_ID = "ps_lock_unlock_button";
+ private static final String LOCK_ICON_RES_ID = "lock_icon";
+ private static final String LOCK_TEXT_RES_ID = "lock_text";
+
+ private final UiObject2 mPrivateSpaceHeader;
+ private final boolean mPrivateSpaceEnabled;
+ private final LauncherInstrumentation mLauncher;
+
+ PrivateSpaceHeader(LauncherInstrumentation launcherInstrumentation,
+ UiObject2 privateSpaceHeader, boolean privateSpaceEnabled) {
+ mLauncher = launcherInstrumentation;
+ mPrivateSpaceHeader = privateSpaceHeader;
+ mPrivateSpaceEnabled = privateSpaceEnabled;
+ verifyPrivateSpaceHeaderState();
+ }
+
+ /** Verify elements in Private Space Header as per state */
+ private void verifyPrivateSpaceHeaderState() {
+ if (mPrivateSpaceEnabled) {
+ verifyUnlockedState();
+ } else {
+ verifyLockedState();
+ }
+ }
+
+ /** Verify Unlocked State elements in Private Space Header */
+ private void verifyUnlockedState() {
+ UiObject2 headerText = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ PS_HEADER_TEXT_RES_ID);
+ mLauncher.assertEquals("PS Header Text is incorrect ",
+ "Private", headerText.getText());
+
+ UiObject2 settingsButton = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ SETTINGS_BUTTON_RES_ID);
+ mLauncher.waitForObjectEnabled(settingsButton, "Private Space Settings Button");
+ mLauncher.assertTrue("PS Settings button is non-clickable", settingsButton.isClickable());
+
+ UiObject2 unLockButtonView = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ UNLOCK_BUTTON_VIEW_RES_ID);
+ mLauncher.waitForObjectEnabled(unLockButtonView, "Private Space Unlock Button");
+ mLauncher.assertTrue("PS Unlock Button is non-clickable", unLockButtonView.isClickable());
+
+ mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ LOCK_ICON_RES_ID);
+
+ UiObject2 lockText = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ LOCK_TEXT_RES_ID);
+ mLauncher.assertEquals("PS lock text is incorrect", "Lock", lockText.getText());
+
+ }
+
+ /** Verify Locked State elements in Private Space Header */
+ private void verifyLockedState() {
+ UiObject2 headerText = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ PS_HEADER_TEXT_RES_ID);
+ mLauncher.assertEquals("PS Header Text is incorrect ",
+ "Private", headerText.getText());
+
+ UiObject2 unLockButtonView = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ UNLOCK_BUTTON_VIEW_RES_ID);
+ mLauncher.waitForObjectEnabled(unLockButtonView, "Private Space Unlock Button");
+ mLauncher.assertTrue("PS Unlock Button is non-clickable", unLockButtonView.isClickable());
+
+ mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ LOCK_ICON_RES_ID);
+
+ mLauncher.waitUntilLauncherObjectGone(SETTINGS_BUTTON_RES_ID);
+ mLauncher.waitUntilLauncherObjectGone(LOCK_TEXT_RES_ID);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index 8d3a631..c58d16e 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -17,9 +17,12 @@
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
+import android.graphics.Rect;
import android.widget.TextView;
import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
import java.util.ArrayList;
@@ -33,10 +36,12 @@
// This particular ID change should happen with caution
private static final String SEARCH_CONTAINER_RES_ID = "search_results_list_view";
protected final LauncherInstrumentation mLauncher;
+ private final UiObject2 mSearchContainer;
SearchResultFromQsb(LauncherInstrumentation launcher) {
mLauncher = launcher;
mLauncher.waitForLauncherObject("search_container_all_apps");
+ mSearchContainer = mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID);
}
/** Find the app from search results with app name. */
@@ -51,18 +56,9 @@
/** Find the web suggestion from search suggestion's title text */
public SearchWebSuggestion findWebSuggestion(String text) {
- ArrayList<UiObject2> webSuggestions =
- new ArrayList<>(mLauncher.waitForObjectsInContainer(
- mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
- By.clazz(TextView.class)));
- for (UiObject2 uiObject: webSuggestions) {
- String currentString = uiObject.getText();
- if (currentString.equals(text)) {
- return createWebSuggestion(uiObject);
- }
- }
- mLauncher.fail("Web suggestion title: " + text + " not found");
- return null;
+ UiObject2 webSuggestion = mLauncher.waitForObjectInContainer(mSearchContainer,
+ getSearchResultSelector(text));
+ return createWebSuggestion(webSuggestion);
}
protected SearchWebSuggestion createWebSuggestion(UiObject2 webSuggestion) {
@@ -72,13 +68,25 @@
/** Find the total amount of views being displayed and return the size */
public int getSearchResultItemSize() {
ArrayList<UiObject2> searchResultItems =
- new ArrayList<>(mLauncher.waitForObjectsInContainer(
- mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
+ new ArrayList<>(mLauncher.waitForObjectsInContainer(mSearchContainer,
By.clazz(TextView.class)));
return searchResultItems.size();
}
/**
+ * Scroll down to make next page search results rendered.
+ */
+ public void getNextPageSearchResults() {
+ final int searchContainerHeight = mLauncher.getVisibleBounds(mSearchContainer).height();
+ // Start scrolling from center of the screen to top of the screen.
+ mLauncher.scroll(mSearchContainer,
+ Direction.DOWN,
+ new Rect(0, 0, 0, searchContainerHeight / 2),
+ /* steps= */ 10,
+ /*slowDown= */ false);
+ }
+
+ /**
* Taps outside bottom sheet to dismiss and return to workspace. Available on tablets only.
* @param tapRight Tap on the right of bottom sheet if true, or left otherwise.
*/
@@ -117,4 +125,16 @@
public SearchResultFromQsb getSearchResultForInput() {
return this;
}
+
+ /** Verify a tile is present by checking its title and subtitle. */
+ public void verifyTileIsPresent(String title, String subtitle) {
+ mLauncher.waitForObjectInContainer(mSearchContainer,
+ getSearchResultSelector(title));
+ mLauncher.waitForObjectInContainer(mSearchContainer,
+ getSearchResultSelector(subtitle));
+ }
+
+ private BySelector getSearchResultSelector(String text) {
+ return By.clazz(TextView.class).text(text);
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
index e2bc17b..6d2631f 100644
--- a/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
+++ b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
@@ -33,9 +33,6 @@
public class SelectModeButtons {
private final UiObject2 mSelectModeButtons;
private final LauncherInstrumentation mLauncher;
-
- private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile(
- "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
"Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
@@ -69,7 +66,6 @@
@NonNull
public Overview dismissByEscKey() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
mLauncher.runToState(
() -> mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE),
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 4e92634..9ac6768 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -66,10 +66,6 @@
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";
-
- static final Pattern EVENT_CTRL_W_DOWN = Pattern.compile(
- "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_W"
- + ".*?metaState=META_CTRL_ON");
static final Pattern EVENT_CTRL_W_UP = Pattern.compile(
"Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_W"
+ ".*?metaState=META_CTRL_ON");
@@ -351,6 +347,7 @@
*/
public Map<String, Point> getWorkspaceIconsPositions() {
final UiObject2 workspace = verifyActiveContainer();
+ mLauncher.waitForLauncherInitialized(); // b/319501259
List<UiObject2> workspaceIcons =
mLauncher.waitForObjectsInContainer(workspace, AppIcon.getAnyAppIconSelector());
return workspaceIcons.stream()
@@ -447,6 +444,8 @@
Runnable expectLongClickEvents) {
try (LauncherInstrumentation.Closable c = launcher.addContextLayer(
"uninstalling app icon")) {
+
+ final String appNameToUninstall = homeAppIcon.getAppName();
dragIconToWorkspace(
launcher,
homeAppIcon,
@@ -471,7 +470,10 @@
try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer(
"uninstalled app by dragging to the drop bar")) {
- return new Workspace(launcher);
+ final Workspace newWorkspace = new Workspace(launcher);
+ launcher.waitUntilLauncherObjectGone(
+ AppIcon.getAppIconSelector(appNameToUninstall));
+ return newWorkspace;
}
}
}
@@ -822,7 +824,6 @@
public Widgets openAllWidgets() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
verifyActiveContainer();
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_DOWN);
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_UP);
mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("pressed Ctrl+W")) {